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


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


3
@NinaScholz:我不明白。没有语法错误;所以我会假设,b.z = 1b.e = 1首先执行(给出右键关联=),然后a.x.y.z = ...执行和失败; 为什么b分配在一种情况下通过而在另一种情况下不通过?
阿玛丹

3
@NinaScholz我们同意该财产y不存在a.x;但这两种情况都是如此。为什么在第二种情况下却不能在第一种情况下阻止右侧分配?执行顺序有什么不同?(我提到语法错误是因为语法错误的时间与运行时错误的时间非常不同。)
Amadan

@Amadan运行代码后,您将得到错误,并且比起再次使用类型变量名称来查看值
Code Maniac

2
发现这描述了Javascript如何进行赋值操作ecma-international.org/ecma-262/5.1/#sec-11.13
所罗门·谭

2
从理论上讲这很有趣,但是这绝对属于“意外行为的原因,这就是为什么您不编写这样的代码”的原因。
约翰·蒙哥马利

Answers:


152

实际上,如果您正确阅读错误消息,则情况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)
}

情况二

// 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文档


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为true,则抛出TypeError异常。

无法将任何内容分配给of的属性undefined-的[[CanPut]]内部方法undefined将始终返回false

换句话说:解释器解析左侧,然后解析右侧,如果无法分配左侧的属性,抛出错误。

当你做

a.x.y = b.e = 1

左侧成功解析,直到PutValue被调用;直到解析右侧后,才会考虑该.x属性求值的事实undefined。解释器将其视为“将一些值分配给undefined的属性y”,并分配给undefinedonly throws inside 的属性PutValue

相反:

a.x.y.z = b.e = 1

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


20
不错的逗号运算符-绝不以这种方式使用它(当然,仅用于调试)!
ecraig12345

2
s / parse / evaluate /
Bergi,

3

考虑以下代码:

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

步骤粗线条执行代码如下要求参考

  1. 评估左侧。请记住两件事:
    • 评估表达式与获取表达式的值并不相同。
    • 评估属性访问器ref例如,a.x.y返回由基本值(未定义)和引用名称()组成的引用refa.xy
  2. 评估右侧。
  3. 获取在步骤2中获得的结果的值。
  4. 将在步骤1中获得的参考值设置为在步骤3中获得的值y,即将undefined 属性设置为该值。这应该引发TypeError异常ref
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.