为什么这些 JavaScript 片段即使都遇到错误,但行为却不同?
var a = {}
var b = {}
try{
a.x.y = b.e = 1 // Uncaught TypeError: Cannot set property 'y' of undefined
} catch(err) {
console.error(err);
}
console.log(b.e) // 1
var a = {}
var b = {}
try {
a.x.y.z = b.e = 1 // Uncaught TypeError: Cannot read property 'y' of undefined
} catch(err) {
console.error(err);
}
console.log(b.e) // undefined
实际上,如果您正确阅读错误消息,案例 1 和案例 2 会引发不同的错误。
案例
a.x.y
:
Cannot set property 'y' of undefined
案例
a.x.y.z
:
Cannot read property 'y' of undefined
我想最好用简单的英语一步一步地执行来描述它。
案例 1
// 1. Declare variable `a`
// 2. Define variable `a` as {}
var a = {}
// 1. Declare variable `b`
// 2. Define variable `b` as {}
var b = {}
try {
/**
* 1. Read `a`, gets {}
* 2. Read `a.x`, gets undefined
* 3. Read `b`, gets {}
* 4. Set `b.z` to 1, returns 1
* 5. Set `a.x.y` to return value of `b.z = 1`
* 6. Throws "Cannot **set** property 'y' of undefined"
*/
a.x.y = b.z = 1
} catch(e){
console.error(e.message)
} finally {
console.log(b.z)
}
案例 2
// 1. Declare variable `a`
// 2. Define variable `a` as {}
var a = {}
// 1. Declare variable `b`
// 2. Define variable `b` as {}
var b = {}
try {
/**
* 1. Read `a`, gets {}
* 2. Read `a.x`, gets undefined
* 3. Read `a.x.y`, throws "Cannot **read** property 'y' of undefined".
*/
a.x.y.z = b.z = 1
} catch(e){
console.error(e.message)
} finally {
console.log(b.z)
}
在评论中, Solomon Tam 发现 有关赋值操作的 ECMA 文档 。
当您利用括号内的逗号运算符来查看在以下情况下执行哪些部分时,操作顺序会更加清晰:
var a = {}
var b = {}
try{
// Uncaught TypeError: Cannot set property 'y' of undefined
a
[console.log('x'), 'x']
[console.log('y'), 'y']
= (console.log('right hand side'), b.e = 1);
} catch(err) {
console.error(err);
}
console.log(b.e) // 1
var a = {}
var b = {}
try {
// Uncaught TypeError: Cannot read property 'y' of undefined
a
[console.log('x'), 'x']
[console.log('y'), 'y']
[console.log('z'), 'z']
= (console.log('right hand side'), b.e = 1);
} catch(err) {
console.error(err);
}
console.log(b.e) // undefined
查看 规范 :
The production
AssignmentExpression : LeftHandSideExpression = AssignmentExpression
is evaluated as follows:
Let lref be the result of evaluating LeftHandSideExpression.
Let rref be the result of evaluating AssignmentExpression.
Let rval be
GetValue(rref)
.Throw a SyntaxError exception if... (irrelevant)
Call
PutValue(lref, rval)
.
PutValue
会引发
TypeError
:
Let O be
ToObject(base)
.If the result of calling the
[[CanPut]]
internal method of O with argument P is false, thena. If Throw is true, then throw a TypeError exception.
无法将任何内容分配给
undefined
的属性 -
undefined
的
[[CanPut]]
内部方法将始终返回
false
。
换句话说:解释器先解析左侧,再解析右侧, 然后 如果左侧的属性无法赋值,则会引发错误。
当您执行
a.x.y = b.e = 1
直到调用
PutValue
之前,左侧都已
成功解析
;直到解析右侧之后,才会考虑
.x
属性的计算结果为
undefined
这一事实。解释器将其视为“将某个值分配给 undefined 的属性“y”,而分配给
undefined
的属性只会在
PutValue
中抛出异常。
相反:
a.x.y.z = b.e = 1
解释器永远不会尝试分配给
z
属性,因为它必须先将
a.x.y
解析为一个值。如果
a.x.y
解析为一个值(甚至解析为
undefined
),那就没问题了 - 会像上面一样在
PutValue
中抛出一个错误。但是
访问
a.x.y
会抛出一个错误,因为无法在
undefined
上访问属性
y
。