前端小白第一次写文章,记录一下一个思考了一下午的问题,这个题是在阅读极客时间周爱民老师《JavaScript核心原理解析》专栏时遇见的
首先先上代码,下面这段代码执行结果是什么?
var a = {n:1};
a.x = a = {n:2};
console.log(a.x); // --> undefined
执行后发现结果是undefined
再看下面一段“基本一样”的代码:
var a = {n:1};
a = {n:2};
a.x = a;
console.log(a.x); // --> {n:2,x:{...}}
执行后发现结果是这样的:
按照大家赋值操作从后往前的常识来说,这两段代码应该是一样的结果,那究竟是哪里出了问题导致打印结果不一样呢?可能也有很多人心里有疑问,为什么第一段执行结果是undefined呢?
我们先来分析第一段代码:
首先,对于第一行,声明一个a变量,赋值为{n:1}
对于第二行连续赋值,《JavaScript 权威指南》有一段话:
JavaScript 总是严格按照从左至右的顺序来计算表达式。
那么按照顺序执行,应该是这样的:
...
a.x = // <- `a` is {n:1}
a = // <- `a` is {n:1}
{n:2}; // 赋值,覆盖当前的左操作数(变量`a`)
a.x先执行,给a这个引用里面添加一个x属性(值得一提的是,a.x本身也是一个表达式,其结果是一个引用),x属性是什么呢?看等号右边,为一个赋值表达式
a = {n : 2},
(这里我们可以把连续赋值看作一个嵌套函数,等号左边都是引用,右边都是值,执行是从前往后,但赋值是一个回调过程,是从后往前的),那么要做的就是求赋值表达式的值;即a={n:2},这是第一次赋值,这时候a这个变量的引用已经发生了变化,指向了另一块地址,这时候过去的a的引用地址就找不到了。那么第二行语句里面的第二次赋值a.x=a就是一个废操作(a.x作为旧的引用被系统回收了)
那么对于第三行,当你再打印a.x时,这里面的a其实是新的引用地址,打印出来自然是undefined。
到这里其实一些聪明的同学已经能反应过来了,第一段代码和第二段代码的区别就在于: 第一段代码第二行里面a.x因为是先执行,所以这个a对应的引用地址对应的是{n:1};而第二段代码里面a.x是在a的引用地址发生改变之后执行的,所以打印新的a.x是能找到这个新地址的。因此也就产生了两个完全不一样的打印结果。
除了以上讲的之外,这道经典问题其实还可以挖掘出JavaScript很多底层知识,如赋值表达式的左手端(lhs)操作数,单值表达式,声明与绑定(var a.x 为什么会报错)等知识,暂时按下不表。