为什么 JavaScript 的这些片段虽然都遇到错误但表现却不同?
Why do these snippets of JavaScript behave differently even though they both encounter an error?
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. 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)
}
案例二
// 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 found this ECMA documentation about assignment operation。
当您利用括号内的逗号运算符来查看执行了哪些部分时,操作顺序会更清晰:
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
查看 spec:
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, then
a. If Throw is true, then throw a TypeError exception.
无法将任何内容分配给 undefined
的 属性 - undefined
的 [[CanPut]]
内部方法将始终 return false
。
换句话说:解释器解析左边,然后解析右边,然后如果左边的属性抛出错误-手边不能分配给。
当你这样做时
a.x.y = b.e = 1
左侧 已成功解析 直到 PutValue
被调用; .x
属性 的计算结果为 undefined
这一事实直到右侧被解析后才被考虑。解释器将其视为“为未定义的 属性 “y” 分配一些值”,并且分配给 undefined
的 属性 只会抛出内部 PutValue
.
对比:
a.x.y.z = b.e = 1
解释器永远不会达到它试图分配给 z
属性 的地步,因为它首先必须将 a.x.y
解析为一个值。如果 a.x.y
解析为一个值(甚至解析为 undefined
),那就没问题了 - 会像上面那样在 PutValue
中抛出错误。但是 accessing a.x.y
抛出一个错误,因为 属性 y
无法在 undefined
.
上访问
考虑以下代码:
var a = {};
a.x.y = console.log("evaluating right hand side"), 1;
执行代码所需的大致步骤如下ref:
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. 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)
}
案例二
// 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 found this ECMA documentation about assignment operation。
当您利用括号内的逗号运算符来查看执行了哪些部分时,操作顺序会更清晰:
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
查看 spec:
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]]
内部方法将始终 return false
。
换句话说:解释器解析左边,然后解析右边,然后如果左边的属性抛出错误-手边不能分配给。
当你这样做时
a.x.y = b.e = 1
左侧 已成功解析 直到 PutValue
被调用; .x
属性 的计算结果为 undefined
这一事实直到右侧被解析后才被考虑。解释器将其视为“为未定义的 属性 “y” 分配一些值”,并且分配给 undefined
的 属性 只会抛出内部 PutValue
.
对比:
a.x.y.z = b.e = 1
解释器永远不会达到它试图分配给 z
属性 的地步,因为它首先必须将 a.x.y
解析为一个值。如果 a.x.y
解析为一个值(甚至解析为 undefined
),那就没问题了 - 会像上面那样在 PutValue
中抛出错误。但是 accessing a.x.y
抛出一个错误,因为 属性 y
无法在 undefined
.
考虑以下代码:
var a = {};
a.x.y = console.log("evaluating right hand side"), 1;
执行代码所需的大致步骤如下ref: