有如许一个热点题目:
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
的呢?简朴地说,按以下步骤:
盘算表达式A,获得一个援用
refA
;盘算表达式B,获得一个值
valueB
;将
valueB
赋给refA
指向的称号绑定;返回
valueB
。
二. 连系性
所谓连系性,是指表达式中同一个运算符涌现屡次时,是左侧的优先盘算照样右边的优先盘算。
赋值表达式是右连系的。这意味着:
A1 = A2 = A3 = A4
等价于
A1 = (A2 = (A3 = A4))
三. 连等的剖析
好了,有了上面两部分的学问。下面来看一下JS引擎是如何运算连等赋值表达式的。
以下面的式子为例:
Exp1 = Exp2 = Exp3 = Exp4
起首依据右连系性,能够转换成
Exp1 = (Exp2 = (Exp3 = Exp4))
然后,我们已晓得关于单个赋值运算,JS引擎老是先盘算左侧的操作数,再盘算右边的操作数。所以接下来的步骤就是:
盘算Exp1,获得Ref1;
盘算Exp2,获得Ref2;
盘算Exp3,获得Ref3;
盘算Exp4,获得Value4。
如今变成了如许的:
Ref1 = (Ref2 = (Ref3 = Value4))
接下来的步骤是:
将Value4赋给Exp3;
将Value4赋给Exp2;
将Value4赋给Exp1;
返回表达式终究的效果Value4。
注重:这几个步骤表现了右连系性。
总结一下就是:
先从左到右剖析各个援用,然后盘算最右边的表达式的值,末了把值从右到左赋给各个援用。
四. 题目的处理
如今回到文章开首的题目。
起首前两个var语句实行完后,a
和b
都指向同一个对象{n: 1}
(为轻易形貌,下面称为对象N1)。然后来看
a.x = a = {n: 2};
依据前面的学问,起首顺次盘算表达式a.x
和a
,获得两个援用。个中a.x
示意对象N1中的x,而a
相当于envRec.a
,即当前环境纪录项中的a。所以此时能够写出以下的情势:
[[N1]].x = [[encRec]].a = {n: 2};
个中,[[]]
示意援用指向的对象。
接下来,将{n: 2}
赋值给[[encRec]].a
,行将{n: 2}
绑定到当前高低文中的称号a
。
接下来,将同一个{n: 2}
赋值给[[N1]].x
,行将{n: 2}
绑定到N1中的称号x
。
由于b
依然指向N1
,所以此时有
b <=> N1 <=> {n: 1, x: {n: 2}}
而a
被从新赋值了,所以
a <=> {n: 2}
而且
a === b.x
五. 末了的末了
假如你邃晓了上面一切的内容,应该会邃晓a.x = a = {n:2};
与b.x = a = {n:2};
是完整等价的。由于在剖析a.x
或b.x
的谁人时候点
。a
和b
这两个称号指向同一个对象,就像C++中同一个对象能够有多个援用一样。而在这个时候点
以后,不论是a.x
照样b.x
,实在早就不存在了,它已变成了谁人内存中的对象.x
了。
末了用一张图示意全部表达式的运算历程: