即使这些JavaScript片段都遇到错误,为什么它们的行为也有所不同?

时间:2019-02-12 09:14:31

标签: javascript operators order-of-execution

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

3 个答案:

答案 0 :(得分:150)

实际上,如果您正确阅读了错误消息,则情况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找到了this ECMA documentation about assignment operation

答案 1 :(得分:56)

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

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

  

产品AssignmentExpression : LeftHandSideExpression = AssignmentExpression的评估如下:

     
      
  1. 让lref是评估LeftHandSideExpression的结果。

  2.   
  3. 让rref成为评估AssignmentExpression的结果。

  4.   
  5. 让rval为GetValue(rref)

  6.   
  7. 如果...(无关),抛出SyntaxError异常

  8.   
  9. 致电PutValue(lref, rval)

  10.   

PutValue是引发TypeError的原因:

  
      
  1. 让我们成为ToObject(base)

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

         

    a。如果Throw为true,则抛出TypeError异常。

  4.   

不能为undefined的属性分配任何内容-[[CanPut]]的{​​{1}}内部方法将始终返回undefined

换句话说:解释器先解析左侧,然后解析右侧,如果不能将左侧的属性分配给然后,则会引发错误

这样做的时候

false

成功解析了左侧 ,直到调用a.x.y = b.e = 1 为止;直到解析完右侧后,才考虑PutValue属性的值为.x的事实。解释器将其视为“为未定义的属性y分配一些值”,而分配给属性undefined仅在undefined内部抛出。

相反:

PutValue

解释器永远不会试图分配给a.x.y.z = b.e = 1 属性,因为它首先必须将z解析为一个值。如果a.x.y解析为一个值(甚至解析为a.x.y),也可以-像上面一样在undefined内抛出错误。但是访问 PutValue会引发错误,因为无法在a.x.y上访问属性y

答案 2 :(得分:3)

考虑以下代码:

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

执行代码所需的步骤的粗略概述如下 ref

  1. 评估左侧。请记住两件事:
    • 计算表达式与获取表达式的值不同。
    • 评估属性访问器 ref 例如a.x.y返回引用 ref ,它由基值a.x(未定义)和引用名称(y)组成。
  2. 评估右侧。
  3. 获取在步骤2中获得的结果的值。
  4. 将在步骤1中获得的参考值设置为在步骤3中获得的值,即将未定义的属性y设置为该值。这应该引发TypeError异常 ref