guyuezhai / interviewSummary

some summary of interview
0 stars 0 forks source link

以下代码输出结果是什么,并解释原因? #31

Open guyuezhai opened 4 years ago

guyuezhai commented 4 years ago
var a = {n: 1};
var b = a;
a.x = a = {n: 2};

console.log(a.x)    
console.log(b.x)
guyuezhai commented 4 years ago

结果: undefined {n:2}

首先,a和b同时引用了{n:2}对象,接着执行到a.x = a = {n:2}语句,尽管赋值是从右到左的没错,但是.的优先级比=要高,所以这里首先执行a.x,相当于为a(或者b)所指向的{n:1}对象新增了一个属性x,即此时对象将变为{n:1;x:undefined}。之后按正常情况,从右到左进行赋值,此时执行a ={n:2}的时候,a的引用改变,指向了新对象{n:2},而b依然指向的是旧对象。之后执行a.x = {n:2}的时候,并不会重新解析一遍a,而是沿用最初解析a.x时候的a,也即旧对象,故此时旧对象的x的值为{n:2},旧对象为 {n:1;x:{n:2}},它被b引用着。 后面输出a.x的时候,又要解析a了,此时的a是指向新对象的a,而这个新对象是没有x属性的,故访问时输出undefined;而访问b.x的时候,将输出旧对象的x的值,即{n:2}。


上面是之前写的解释,最近看周爱民老师的文章的时候,发觉这部分解释有不少地方没说到本质上,有的还是错误的,所以我重新结合老师的文章研究了一下,修改如下: 以这段代码为例:

var a = {n:1};
a.x = a ={n:2};
console.log(a.x);  
代码 注释 补充
a 计算单值表达式 a,得到 a 的引用 这里的 a 是初始 a
a.x 将 x 这个标识符作为. 运算符的右操作数,计算表达式 a.x,得到结果值(Result),它是一个 a.x 的“引用” 这个“引用”当作一个数据结构,通常有 base、name、strict 三个成员。无论x 属性是否存在(这里暂时不存在),a.x 都会被表达为 {"base": a, "name": "x", ...}。而这里的 a 仍然指向旧对象。
a 计算单值表达式 a,得到 a 的引用 这里的 a 是初始 a
a = {n:2} 赋值操作使得左操作数 a 作为一个引用被覆盖,同时操作完成后返回右操作数 {n:2} 这里的这个 a 的的确确被覆盖了,这意味着往后通过 a 访问到的只能是新对象。但是,有一个 a 是不会变的,那就是被 a.x 的 Result 保存下来的引用 a,它作为一个当时既存的、不会再改变的结果,仍然指向旧对象。
a.x = {n:2} 指向旧对象的 a 新建了 x 属性,这个属性关联对象 {n:2} 注意,这里对 a.x 进行了写操作(赋值),直到这次赋值发生的那一刻,才有了为旧对象动态创建 x 属性这个过程。

所以,旧对象(丧失了引用的最初对象)和新对象(往后通过 a 可以访问到的那个对象)分别变成:

// 旧对象
a:{
    n:1,
    x:{n:2}
}
// 新对象
a:{
    n:2
}

现在,执行 console.log(a.x),这里 a.x 被作为 rhs(右手端) 读取,引擎会开始检索是否真的有 a["x"] 这个东西,因为此时通过 a 能访问到的只能是新对象,它自然是没有 x 属性的,所以打印 undefined。而且 —— 直到这次读取发生的那一刻,才有了为新对象动态创建 x 属性这个过程。

Note:也就是说,在引擎从左到右计算表达式的过程中,尽管可能遇见类似 a.x 这样本不存在的属性,但无论如何,都会存在 {"base": a, "name": "x", ...} 这样的数据结构,而在后续真正对 x 进行 读写 的时候,这个 x 才会得到创建。

这个代码块所做的事情,实际上是向旧有对象添加一个指向新对象的属性,并且如果我们想要在后续仍然持有对旧对象的访问,可以在赋值覆盖之前新建一个指向旧对象的变量。

Originally posted by @Chorer in https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/93#issuecomment-482389152