第 53 题:输出以下代码的执行结果并解释为什么

发布于 2022-10-06 22:22:57 字数 187 浏览 115 评论 16

var a = {n: 1};
var b = a;
a.x = a = {n: 2};

console.log(a.x) 	
console.log(b.x)

这是连续赋值的坑,更多信息见参考资料

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

盗心人 2022-05-04 13:56:45

为啥不会重新解析a啊

離人涙 2022-05-04 13:56:45

有一个网站可以将 JavaScript 代码的执行过程,用可视化的方式呈现出现。具体链接如下:tylermcginnis

从可视化的执行过程来看,并没有之前上面答案所说的对象增加 x 属性的这个过程,也即 { n: 1 } => { n: 1, x: undefined },而是最后直接变成 { n: 1, x: { n: 2 } }

const a = {};
const b = 1;
a.x = b;

第三行代码 a.x = b; 在执行的过程中,会执行一次左查询以及一次右查询。这里所说的“左” / “右”是把 = 操作符作为参照物,a.x 执行左查询是为了弄清楚在 a 对象上是否存在 x 属性,如果存在,那么 a.x = b; 语句执行的是更新属性的操作;反之,则是新增属性的操作。如果在查询的过程中,发现 a 不存在,则引擎会报错。b 执行右查询是为了获取 b 的值。如果在查询的过程中,发现 b 不存在,引擎可能报错,也可能不报错。至于在赋值的过程中,是否执行左查询或者右查询,关键是看 = 的左右两边是否存在变量

这个问题考察的知识点主要有以下这些:

  • . 的优先级高于 = 的优先级
  • = 具有右结合性(执行的方向是从右往左,先执行 = 右边的表达式,然后把结果赋值给 = 左边的表达式,从这里可以得出 = 属于二元操作符),多个 = 的执行过程,可以类比成"递归"的过程
let a = { n: 1 };
const b = a;

a.x = a = { n: 2 }; 

执行完第一行以及第二行代码之后,变量 a 和 常量 b 指向同一块内存地址(对象 { n: 1 } 在内存里面的内存地址)。换句话说,a 现在是 b 的别名;反之亦然

在执行第三行代码之前,你要知道 a.x = a = { n: 2 } 里面包含两种操作符(.=)。也正是由于 . 的优先级高于 = 的优先级,所以会首先执行 a.x。不过在执行 a.x 的过程中,会执行一次“左”查询。经过左查询之后,发现对象 a 没有 x 属性(在这里你可以认为代码已经变成 ({ n: 1 }).x 或者 b.x),然后会再去执行第一个 = 操作符。由于 = 具有右结合性,所以会先去执行 a = { n: 2 }。在执行的过程中,发现 a = { n: 2 } 是一个普通的赋值操作。而且也正是因为 = 右边是一个对象字面量,所以在这里是不存在右查询以及表达式的计算过程。不过在把 { n: 2 } 赋给变量 a 之前,需要对变量 a 执行一次左查询。经过左查询之后,发现变量 a 已经被声明(假如发现变量 a 没有被声明,在非严格模式下,对它赋值的这个操作不会导致引擎报错),所以会继续把 { n: 2 } 赋值给变量 a。之后会把 a = { n: 2 } 语句的返回结果,作为第一个 = 右边的表达式。所以第三行代码变成 ({ n: 1 }).x = { n: 2 } 或者 b.x = { n: 2}。如果没有第二行代码 const b = a;,在执行完第三行代码之后,对象 { n: 1, x: { n: 2} } 所占据的内存会被 GC 回收。补充一句,假设第三行代码就只有 a.x 的话,那么第三行代码的执行过程就结束啦。

至于想搞清楚自己到底有没有理解这个,可以尝试想一下:如果 . 的优先级低于 = 的优先级,上述代码的执行过程是怎样的?

let a = { n: 1 };
const b = a;

// `.` 的优先级低于 `=` 的优先级
a.x = a = { n: 2 };
console.log(a); // 报错

简单的分析一下,a.x = a = { n: 2 }; 这段代码,最后会演变成 (a = { n: 2 }, x = (a = { n: 2 }), a.(x = (a = { n: 2 })))。简化一下会变成这样:(a = { n: 2 }, x = a, a.{ n: 2 })

安稳善良 2022-05-04 13:56:45

连续赋值

var a = {n: 1};
var b = a;
a.x = a = {n: 2};

console.log(a.x) 	
console.log(b.x)
  1. a 赋值,a 指向堆内存 {n:1}
a = { n : 1 }
  1. b 赋值,b 也指向对内存 {n:1}
b = a
  1. .的优先级大于=,所以优先赋值。ps:此时a.x已经绑定到了{n: 1 , x: undefined}被等待赋值
a.x = undefined

a // {n: 1 , x: undefined}
b // 也指向上行的堆内存
  1. 同等级赋值运算从右到左,a改变堆内存指向地址,所以a = {n: 2},
a.x = a = {n: 2};
  1. 因为a.x已经绑定到了{n: 1 , x: undefined}这个内存地址,所以相当于
{n: 1 , x: undefined}.x = {n: 2}
  1. 结果
a = {n: 2}
b = {
  n: 1,
  x: {
    n: 2
  }
}
遗忘曾经 2022-05-04 13:56:45

之前我也一直想为什么不会重新解析a,然后看了上面很多分析,但还是有些看不明白,然后看到点的优先级大于等号的优先级,那我可以这么理解吗?
var a = {n: 1};
var b = a;
a.x={n:2};
a = {n:2};

快乐很简单 2022-05-04 13:56:45

来凑个热闹 ...

把 a.x = a = {n: 2} 拆分来分析
1. [0x001].x = ...
2. a = {n: 2}
3. [0x001] = a 

解析js连续赋值的坑

蹲在坟头点根烟丶 2022-05-04 13:56:45

@yeyi361936738 我不认同你的这个观点

. 的优先级大于 =,所以优先赋值。ps:此时 a.x 已经绑定到了 {n: 1 , x: undefined} 被等待赋值

你这句话恰好说明你不懂 . 的优先级大于 = 是什么意思

@onloner2012 @ruyanzhang 至于为什么不会重新解析 a,这恰恰是说明 . 操作符的优先级高于 =。因为 . 操作符(a.x)在一开始就已经被执行过,所以这时候你可以把 a.x 理解成 ({ n: 1 }).x。如果引擎在赋值操作(= 操作符属于二元操作符)的过程中,又去访问 a(重新解析 a),势必又会去执行 . 操作符,不就说明 . 操作符的优先级低于 =,这与事实(. 操作符的优先级高于 =)矛盾,所以此时 = 操作符是把 a.x 看作一个整体,不会重新解析 a,不过存在重新解析 a 的情况:

let a = {};
let b;

[b = a] = [, a = {n: 2}];

现在继续来解释下面这一段代码

const a = {};
a.x = void 1024;

由于 . 的优先级大于 =,所以首先执行的是对象属性的 get 操作(又称之为左查询),通过执行该查询操作之后,发现 a 对象本身以及原型链上都不存在该属性(x);此时也意味着 . 操作符的执行先告一段落。假设第二行代码里面没有 = 操作符(a.x;),到这里也就意味着,第三行代码的执行过程全部结束。然后引擎继续从 x 的位置开始向右执行(可能你会问我为什么是向右执行,这是因为不同操作符的执行顺序由该操作符本身的优先级决定的,自然执行完 . 操作符,然后引擎去执行 =,恰好 = 操作符出现在 . 操作符的右边),然后在执行的过程中遇到 = 操作符,由于 = 操作符具有右结合性,也就意味着这时候会首先执行 = 右边的表达式,所以在上述表达式的计算过程结束后,得到计算值 undefined ,并且将该计算值(undefined)赋给 a.x。可能你会问我为什么是赋值?这是因为 = 操作的作用是赋值以及该操作符是二元操作符。正如之前所提到的,{ n: 1 } 或者 b 对象上没有 x 属性,这也就意味着此时的赋值操作是 set 操作,所以最后的结果({ n: 1 } 或者 b 对象上新增一个属性值为 undefined 的属性 x)也正如你所看到的那样。

顺便说一句,往对象上新增一个属性或者修改已存在属性的属性值,不一定能成功。以下是一些

雨落□心尘` 2022-05-04 13:56:07
var a = {n: 1}; // a保持对{n:1}对象的引用
var b = a; // b保持对{n:1}对象的引用
a.x = a = {n: 2}; // a的引用被改变

a.x 	// --> undefined
b.x 	// --> {n: 2}

1、.运算符优先,a.x此时保持对{n: 1}的引用,也就是b也保持对{n: 1}的引用,于是{n: 1} => {n: 1, x: undefined},此时a和b还是对原来对象的引用,只不过原来对象增加了x属性
2、=从右往左,a = {n: 2},此时a的引用已经变成了{n: 2}这个对象
3、a.x=a,此时a.x是保持对{ n: 1, x: undefined}中的x引用,也就是b.x,于是{ n: 1, x: undefined} => {n: 1, x: { n: 2}},即b.x = { n: 2 }

稀香 2022-05-04 13:55:08

答案如上
注意点:
1: 点的优先级大于等号的优先级
2: 对象以指针的形式进行存储,每个新对象都是一份新的存储地址

半夏半凉 2022-05-04 13:54:24

运算符优先级还真没注意,涨姿势了

躲猫猫 2022-05-04 02:32:31

以前有做过一个一样的题,等号运算符和.运算符优先级的问题。

https://youzixr.github.io/2019/03/05/JS-%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95/

笑忘罢 2022-05-04 00:40:28

把 a.x = a = {n: 2}, 换成 b.x = a = {n: 2} 的时候,是不是会好理解了,虽然确实是这样。

阪姬 2022-05-03 14:02:59

undefined
{n: 2}
具体答案分析和扩展之前写过一篇类似的
https://juejin.im/post/5b605473e51d45191a0d81d8

倾城°AllureLove 2022-05-01 18:07:37

结果:
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

~没有更多了~

关于作者

梦纸

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

1CH1MKgiKxn9p

文章 0 评论 0

ゞ记忆︶ㄣ

文章 0 评论 0

JackDx

文章 0 评论 0

信远

文章 0 评论 0

yaoduoduo1995

文章 0 评论 0

霞映澄塘

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文