转载

从ES规范中学JS(二):深入理解“连等赋值”问题

有这样一个热门问题:

var a = {n:1}; var b = a; a.x = a = {n:2}; alert(a.x); // --> undefined alert(b.x); // --> {n:2}

其实这个问题很好理解,关键要弄清下面两个知识点:

  • JS引擎对 赋值表达式 的处理过程

  • 赋值运算的 右结合性

赋值表达式

形如

 A = B

的表达式称为 赋值表达式 。其中A和B又分别可以是表达式。B可以是任意表达式,但是A必须是一个 左值

所谓左值,就是可以被赋值的表达式,在ES规范中是用内部类型 引用(Reference) 描述的。例如:

  • 表达式 foo.bar 可以作为一个左值,表示对foo这个对象中bar这个名称的引用;

  • 变量 email 可以作为一个左值,表示对当前执行环境中的环境记录项envRec中email这个名称的引用;

  • 同样地,函数名 func 可以做左值,然而函数调用表达式 func(a, b) 不可以。

那么JS引擎是怎样计算一般的赋值表达式 A = B 的呢?简单地说,按如下步骤:

  1. 计算表达式A,得到一个引用 refA

  2. 计算表达式B,得到一个值 valueB

  3. valueB 赋给 refA 指向的名称绑定;

  4. 返回 valueB

结合性

所谓结合性,是指表达式中同一个运算符出现多次时,是左边的优先还是右边的优先计算。

赋值表达式是右结合的。这意味着:

A1 = A2 = A3 = A4

等价于

A1 = (A2 = (A3 = A4))

连等的解析

好了,有了上面两部分的知识。下面来看一下JS引擎是怎样运算连等赋值表达式的。

以下面的式子为例:

Exp1 = Exp2 = Exp3 = Exp4

首先根据右结合性,可以转换成

Exp1 = (Exp2 = (Exp3 = Exp4))

然后,我们已经知道对于单个赋值运算,JS引擎总是先计算左边的操作数,再计算右边的操作数。所以接下来的步骤就是:

  1. 计算Exp1,得到Ref1;

  2. 计算Exp2,得到Ref2;

  3. 计算Exp3,得到Ref3;

  4. 计算Exp4,得到Value4。

现在变成了这样的:

Ref1 = (Ref2 = (Ref3 = Value4))

接下来:

  1. 将Value4赋给Exp3;

  2. 将Value4赋给Exp2;

  3. 将Value4赋给Exp1;

  4. 返回表达式最终的结果Value4。

注意:这个步骤体现了右结合性。

结束。

问题解决

待续。要睡午觉了...

正文到此结束
Loading...