对Js赋值运算的新认识

leozdgao

可以在这里看:http://leozdgao.me/renew-js-assignment/

此文的目的是为了解释如下现象:

var foo = { n: 1 };
var bar = foo;
foo.x = foo = { n: 2 };
console.log(foo.x); // undefined

赋值运算符

根据ECMA规范中的定义赋值运算符的产生式(production)以及运算过程如下:

The production AssignmentExpression : LeftHandSideExpression = AssignmentExpression is evaluated as follows:

  1. Let lref be the result of evaluating LeftHandSideExpression.

  2. Let rref be the result of evaluating AssignmentExpression.

  3. Let rval be GetValue(rref).

  4. Throw a SyntaxError exception if the following conditions are all true:

    • Type(lref) is Reference is true

    • IsStrictReference(lref) is true

    • Type(GetBase(lref)) is Environment Record

    • GetReferencedName(lref) is either "eval" or "arguments"

  5. Call PutValue(lref, rval).

  6. Return rval.

其实第一次看这个部分我也还是无法理解开始列出的那个代码中结果到底是为什么,看了其他博客其实也没有讲清楚,在StackOverflow上相关问题中有解释到说是因为首先解析左边表达式时确定了引用的指向,而只看11.13.1的话,只有一句“获取LeftHandSideExpression的计算结果”,所以我觉得要真正理解,只看11.13.1是不行,还需要了解下11.2.1关于Property Accessors的内容,从而知道这个“计算结果是如何得到的”:

The production MemberExpression : MemberExpression [ Expression ] is evaluated as follows:

  1. Let baseReference be the result of evaluating MemberExpression.

  2. Let baseValue be GetValue(baseReference).

  3. Let propertyNameReference be the result of evaluating Expression.

  4. Let propertyNameValue be GetValue(propertyNameReference).

  5. Call CheckObjectCoercible(baseValue).

  6. Let propertyNameString be ToString(propertyNameValue).

  7. If the syntactic production that is being evaluated is contained in strict mode code, let strict be true, else let strict be false.

  8. Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.

关键点在于它的返回值,用一个栗子来解释就是说:如果有表达式foo.x,则它的返回值是一个指向foo对象x属性的引用

那么在知道了这一点后,开始解释上面的现象:

首先是两个变量的声明和初始化,var foo = { n: 1 }; var bar = foo;,这个很好理解,就是foo和bar同时指向了一个相同的对象{ n: 1 }

接下来,对于表达式foo.x = foo = { n: 2 };,我们都知道它实际上等于是foo.x = (foo = { n: 2 })。我们开始应用上ECMA规范上的步骤,虽然赋值运算符具有右结合性,然而它首先做的是得到表达式foo.x的值,根据我们对Property Accessors的解释它返回一个指向对象{ n: 1}的x成员的引用,需要注意的是,这个时候foo并没有改变引用的指向。

继续,开始计算右边的结果,就是让foo指向另外的一个对象{n: 2},返回值就是其右边运算式(operand)的结果,即对象{n: 2}这个容易理解。

那么现在应该清楚了,赋值语句中foo.x的结果是指向对象一成员x的引用,而下面的console.log(foo.x)中的foo指向的是对象二,所以这里foo.x返回undefined就理所当然了。

所以试着输出对象一,即bar(因为它从始至终指向的是对象一):

{ n: 1, x: { n: 2 } }

如果有其他看法,接受各种形式的补充和指正。

阅读 5.3k

leozdgao
知识分享

前端爱好者

4k 声望
735 粉丝
0 条评论

前端爱好者

4k 声望
735 粉丝
文章目录
宣传栏