理解javascript中的连续赋值
之前在扒源码时经常看到类似的连续赋值操作:
var a = b = 1;
在某度搜了众多前辈的博客,总算对这骚操作有点眉目。
Case analysis
首先,javascript中连续赋值最典型案例是:
var a = {n: 1}; var b = a; a.x = a = {n: 2}; console.log(a.x); //undefined console.log(b.x); //{n: 2}
Analysis in short
var a = {n: 1}; //创建对象{n: 1},a指向对象{n: 1} var b = a; //将 a中的地址传给b,即 b也指向对象{n: 1} a.x = a = {n: 2}; /* 1)创建对象{n: 1}中的"x"属性 2)将a指向新对象{n: 2} 3) 将对象{n: 1}中的"x"指向新对象{n: 2} */ console.log(a.x); //undefined console.log(b.x); //{n: 2}
如果看懂了,下面可以直接忽略2333
Analysis in long
我归纳了一下,要看懂这个问题要理解三个点:
-
此处的a,b是引用类型
-
在javascript中字段访问操作符"."的优先级高于赋值操作符"="
-
出现多个赋值操作符"="时,运算顺序为从右向左
1. 此处的a,b是引用类型
引用类型是区别于基本类型的。javasript中的基本类型总共有5种,Undefined、Null、String、Number、Boolean。引用类型有Object,Array,Function,,,blabla~,简单说就是除了5种基本类型,可以说其它都为引用类型。
案例中的{n: 1}和{n: 2}都为Object类,即为引用类型。在javascript中当复制保存某个对象时,操作的是对象的引用,但在为对象添加属性时,操作的是实际的对象。即在第一行和第二行命令中a,b其实存储的是对象{n: 1}的引用(地址)。
这有点像C中的指针。如下图,案例第一行命令可以解析为,在堆(heap)中创建了一个对象{n: 1},另外在栈(stack)中创建了变量a,变量a的值为对象{n: 1}的地址,即图中的h[0]。第二行命令意思是再在栈中创建另一个变量b,变量b的值与变量a的值相等,即为对象{n: 1}的地址。
打个比方,你有一张去餐馆的地图,你把地图复制一份给你的基友,地图是多了一张,但餐馆的实际位置是没有改变的。
2. 运算顺序问题
这一节对应的是第二和第三个点。根据javascript中的运算符优先级 Operator precedence,可知:
- 在javascript中字段访问操作符"."的优先级高于赋值操作符"="
- 出现多个赋值操作符"="时,运算顺序为从右向左
因此在运行第三行命令时,分3步执行:
(1)先执行"a.x",此时a储存的仍然是h[0]地址,即指向对象{n: 1}。"a.x"相当于"h[0].x",即在对象{n: 1}中添加了属性"x",变为{n: 1; x: undefined},但是"x"还没赋值;注意,此时对象{n: 1; x: undefined}中的"x"属性是等待赋值的,操作挂起。
(2)再执行"a = {n: 2}",由于{n: 2}是个新对象,则程序在堆中再创建一个对象{n: 2},并且将变量a指向该对象,如图,此时a储存的即为h[1]的地址
(3)最后一步"a.x = a",(1)中被挂起的操作继续执行,对象{n: 1; x: undefined}中的"x"指向对象{n: 2}。运算完成。
3. 结果解释
在运行完上述命令后,变量a指向了新对象{n : 2};变量b 的地址没有发生改变,因而仍指向修改后的对象{n: 1;x: {n : 2}}。
var a = {n: 1}; var b = a; a.x = a = {n: 2}; console.log(a.x); //undefined console.log(b.x); //{n: 2}