107

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

4

3 回答 3

151

实际上,如果您正确阅读错误消息,案例 1 和案例 2 会抛出不同的错误。

案例a.x.y

无法设置未定义的属性“y”

案例a.x.y.z

无法读取未定义的属性“y”

我想最好用简单的英语逐步执行来描述它。

情况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 文档

于 2019-02-12T09:45:24.643 回答
57

当您利用括号符号内的逗号运算符来查看在以下情况下执行哪些部分时,操作顺序会更加清晰:

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

查看规格

生产AssignmentExpression : LeftHandSideExpression = AssignmentExpression评估如下:

  1. 令 lref 为评估 LeftHandSideExpression 的结果。

  2. 令 rref 为评估 AssignmentExpression 的结果。

  3. 设 rval 为GetValue(rref)

  4. 如果...(无关),则抛出 SyntaxError 异常

  5. 打电话PutValue(lref, rval)

PutValue是什么抛出TypeError

  1. 让 O 成为ToObject(base)

  2. 如果[[CanPut]]使用参数 P 调用 O 的内部方法的结果为 false,则

    一种。如果 Throw 为真,则抛出 TypeError 异常。

没有任何东西可以分配给 的属性undefined- 的[[CanPut]]内部方法undefined将始终返回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像上面一样在里面抛出一个错误。但是访问 a.x.y会引发错误,因为y无法在 上访问属性undefined

于 2019-02-12T09:45:26.023 回答
3

考虑以下代码:

var a = {};
a.x.y = console.log("evaluating right hand side"), 1;

执行代码所需步骤的大致轮廓如下ref

  1. 评估左侧。要记住两件事:
    • 评估表达式与获取表达式的值不同。
    • 评估一个属性访问器ref例如a.x.y返回一个由基值(未定义)和引用名称( )组成的引用refa.xy
  2. 评估右手边。
  3. 获取第 2 步得到的结果值。
  4. 将第1步得到的reference的值设置为第3步得到的值,y即将undefined的属性设置为该值。这应该抛出一个 TypeError 异常ref
于 2019-02-13T10:26:30.223 回答