let a = {n:1};
let b = a;
a.x = a = {n:2};
console.log(a.x);// undefined
很多人没仔细考虑的话,会认为 a.x 应该是 {n:2},因为我们都知道,"=" 赋值运算符是自右向左的:
a.x = a = {n:2};
这句话就可以理解为 : 将 a = {n:2} (后面称为对象B)的返回值赋值给 a.x。这么想的话,就忽略了这个表达式里包含的另一个运算符 “.”,即字段访问运算符(或者叫成员运算符?),明显字段访问运算符优先级是要比赋值运算符要高的,因此,这段代码在进行赋值运算前,会先解析 a.x,此时 a 还指向 {n:1}(后面称为对象A),对象A中找不到x属性,则给A增加x 属性,值为 undefined,A变成 {n:1, x:undefined}。然后进行赋值运算将 a和对象A的x属性指向对象B,此时 b = A = {n:1, x:{n:2}}, a = B = {n:2}; 很明显 对象B是没有x属性的,a.x自然打印 undefined。下面的图更形象一些:
第一行: let a = {n:1},在堆内存中创建 对象A,栈中创建变量 a 指向 对象A。
第二行:let b = a,栈中创建变量 b 指向 a指向的堆中的 对象A。
第三行:a.x = a = {n:2},这里可以分成两步:
第一步:“计算” a.x,给堆中 对象A 增加 x属性,不赋值即为 undefined。
第二步计算 a.x = a = {n:2}:
首先在堆中创建 对象B,将 a 指向 对象B。因为 a.x 的计算是在此赋值之前,所以此时 a.x 指向的是 对象A 的 x属性。
然后将 对象A 的 x属性 指向对象B。