cnblogs标题: JS连等赋值的坑
关于JS连等赋值有个经典的笔试题:
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
console.log(a.x); // --> undefined
console.log(b.x); // --> {n: 2}
咋一看, 一脸懵逼, 这都什么玩意. 我一开始也是这个想法, 不过理解之后发现, 不是题目坑,
确实自己水平还不到位. 本文先介绍理解上述笔试题需要的知识点, 然后对该笔试题详细分析.
单个赋值表达式
形如A = B
的表达式被称为赋值表达式, 其中A和B又分别可以是表达式, B可以是任意表达式,
而A必须是可以被赋值的表达式. 最关键的是我们要理解JS引擎是如何解析赋值表达式的:
- 先计算表达式A, 得到一个引用refA;
- 再计算表达式B, 得到一个值valueB;
- 将valueB赋给refA指向的位置;
- return valueB;
这4个步骤最核心的是规定了, 必须先计算A, 再计算B. 我们在文末分析笔试题就会看到.
多个连等解析
A1 = A2 = A3 = A4
, 知道单个赋值表达式的解析逻辑, 多个连等赋值就很容易类推. 出现
多个连等, 我们完全可以给它分解成单个等号的形式. 比如上式可以分解成下面这样:
A1 = (A2 = A3 = A4)
, 左边的A1看成单个赋值表达式中的A, 右边整体看成B. 继续分解,
最终得出这样A1 = (A2 = (A3 = A4))
. 因此这个连等的按步骤执行如下:
- 依次计算A1, A2, A3, 分别得到refA1, refA2, refA3;
- 计算A4得到valueA4, 把valueA4赋给A3;
- 把(A3 = A4)这个赋值表达式的返回值, 也就是value4赋给A2;
- 把(A2 = (A3 = A4))这个赋值表达式的返回值, 也就是value4赋给A1;
大家不要蒙圈, 就在脑中这样想, 先计算左后计算右. 左边就单个A1, OK直接计算. 来到右
边发现没法直接计算, 那么就把右边再分成左右, 按照这种思路循环递推就行.
和学二叉树时候的思路, 简直如出一辙.
再看笔试题
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
console.log(a.x); // --> undefined
console.log(b.x); // --> {n: 2}
你可能会发现, 现在貌似还是不理解上面的代码到底发生了什么, 说明对JS中引用赋值, 理解还不
够透彻.
上面总共5行代码, 按顺序编号1-5.
- 首先执行前2行代码, 我们脑中大致有这样的图.
a --> [ {n: 1} ] <--b
, 说这是图太勉强, 大家
将就看_. 这个图中间的[]
表示这是个盒子, 盒子里面装有{n: 1}
这个对象,a
和b
都指向这个
对象. - 接下来, 到了全文最关键的时刻. 按照我们前面的分析, 第3行代码先执行
a.x
, 这时候我们上面的
的图已经发生变化了, 变成这样a --> [ {n: 1, x: } ] <--b
, 我们的x
同学已经准备好了, 等着
别人给它赋值呢. - 再接着执行
a
, 我们的图没发生变化. - 在接着执行
{n: 2}
, 大家注意这可是我们全新召唤出来的盒子, 盒子里面装有数据{n: 2}
. 该
盒子和前面的{n: 1}
盒子没有任何关系(到目前为止). - 为了方面我们就把第一次出现的盒子叫做盒子1, 第2次出现的的字叫做盒子2. 现在我们把盒子2赋给
a
, 我们的脑中将出现2个图. 图1[ {n: 1, x: } ] <--b
, 图2a --> [ {n: 2} ]
. 也就是说
a
已经指向了盒子2, 而b
仍旧指向盒子1. - 我们把
a = {n: 2}
这个表达式的返回值{n: 2}
赋给x
同学, 对就是x
,x
一直在等着呢, 现在
没有a
什么事了. 最终我们的图变成了这样, 图1[ {n: 1, x: {n: 2} } ] <--b
,
图2a --> [ {n: 2} ]
.
我们上述6个步骤图的变化单门拿出来:
a --> [ {n: 1} ] <--b
a --> [ {n: 1, x: } ] <--b
[ {n: 1, x: } ] <-- b
a --> [ {n: 2} ]
// 最终图
[ {n: 1, x: {n: 2} } ] <--b
a --> [ {n: 2} ]
现在让我们输出什么, 我们就能输出什么.
console.log(a.x); // a现在指向的盒子2, 盒子2里没有x, 输出undefined
console.log(b.x.n); // 2