var a = {n: 1}; var b = a; a.x = a = {n: 2}; console.log(a.x) console.log(b.x)
这是连续赋值的坑,更多信息见参考资料。
上一篇: 第 54 题:冒泡排序如何实现,时间复杂度是多少, 还可以如何改进?
下一篇: 在 macOS 中连接 Github 服务
为啥不会重新解析a啊
有一个网站可以将 JavaScript 代码的执行过程,用可视化的方式呈现出现。具体链接如下:tylermcginnis
从可视化的执行过程来看,并没有之前上面答案所说的对象增加 x 属性的这个过程,也即 { n: 1 } => { n: 1, x: undefined },而是最后直接变成 { n: 1, x: { n: 2 } }。
{ 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 不存在,引擎可能报错,也可能不报错。至于在赋值的过程中,是否执行左查询或者右查询,关键是看 = 的左右两边是否存在变量
a.x = b;
=
a.x
a
x
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 的话,那么第三行代码的执行过程就结束啦。
a.x = a = { n: 2 }
({ n: 1 }).x
b.x
a = { n: 2 }
{ n: 2 }
({ n: 1 }).x = { n: 2 }
b.x = { n: 2}
const b = a;
{ n: 1, x: { n: 2} }
至于想搞清楚自己到底有没有理解这个,可以尝试想一下:如果 . 的优先级低于 = 的优先级,上述代码的执行过程是怎样的?
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 })。
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 })
引用类型的原因,我之前写了一下https://github.com/marsprince/lelouch/blob/master/docs/learn/about-point.md
a = { n : 1 }
b = a
a.x = undefined a // {n: 1 , x: undefined} b // 也指向上行的堆内存
a.x = a = {n: 2};
{n: 1 , x: undefined}.x = {n: 2}
a = {n: 2} b = { n: 1, x: { n: 2 } }
之前我也一直想为什么不会重新解析a,然后看了上面很多分析,但还是有些看不明白,然后看到点的优先级大于等号的优先级,那我可以这么理解吗?var a = {n: 1};var b = a;a.x={n:2};a = {n:2};
来凑个热闹 ...
把 a.x = a = {n: 2} 拆分来分析 1. [0x001].x = ... 2. a = {n: 2} 3. [0x001] = a
解析js连续赋值的坑
@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)也正如你所看到的那样。
get
a.x;
undefined
set
顺便说一句,往对象上新增一个属性或者修改已存在属性的属性值,不一定能成功。以下是一些
【进阶1-3期】JavaScript深入之内存空间详细图解
进阶系列中有的
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 }
{n: 1}
{n: 1, x: undefined}
a = {n: 2}
{n: 2}
a.x=a
{ n: 1, x: undefined}
{n: 1, x: { n: 2}}
b.x = { n: 2 }
答案如上注意点:1: 点的优先级大于等号的优先级2: 对象以指针的形式进行存储,每个新对象都是一份新的存储地址
运算符优先级还真没注意,涨姿势了
以前有做过一个一样的题,等号运算符和.运算符优先级的问题。
https://youzixr.github.io/2019/03/05/JS-%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95/
把 a.x = a = {n: 2}, 换成 b.x = a = {n: 2} 的时候,是不是会好理解了,虽然确实是这样。
undefined{n: 2}具体答案分析和扩展之前写过一篇类似的https://juejin.im/post/5b605473e51d45191a0d81d8
结果: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);
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
暂无简介
文章 0 评论 0
接受
发布评论
评论(16)
为啥不会重新解析a啊
有一个网站可以将 JavaScript 代码的执行过程,用可视化的方式呈现出现。具体链接如下:tylermcginnis
从可视化的执行过程来看,并没有之前上面答案所说的对象增加 x 属性的这个过程,也即
{ n: 1 }
=>{ n: 1, x: undefined }
,而是最后直接变成{ n: 1, x: { n: 2 } }
。第三行代码
a.x = b;
在执行的过程中,会执行一次左查询以及一次右查询。这里所说的“左” / “右”是把=
操作符作为参照物,a.x
执行左查询是为了弄清楚在a
对象上是否存在x
属性,如果存在,那么a.x = b;
语句执行的是更新属性的操作;反之,则是新增属性的操作。如果在查询的过程中,发现a
不存在,则引擎会报错。b
执行右查询是为了获取b
的值。如果在查询的过程中,发现b
不存在,引擎可能报错,也可能不报错。至于在赋值的过程中,是否执行左查询或者右查询,关键是看=
的左右两边是否存在变量这个问题考察的知识点主要有以下这些:
.
的优先级高于=
的优先级=
具有右结合性(执行的方向是从右往左,先执行=
右边的表达式,然后把结果赋值给=
左边的表达式,从这里可以得出=
属于二元操作符),多个=
的执行过程,可以类比成"递归"的过程执行完第一行以及第二行代码之后,变量
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
的话,那么第三行代码的执行过程就结束啦。至于想搞清楚自己到底有没有理解这个,可以尝试想一下:如果
.
的优先级低于=
的优先级,上述代码的执行过程是怎样的?简单的分析一下,
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 })
。引用类型的原因,我之前写了一下https://github.com/marsprince/lelouch/blob/master/docs/learn/about-point.md
连续赋值
之前我也一直想为什么不会重新解析a,然后看了上面很多分析,但还是有些看不明白,然后看到点的优先级大于等号的优先级,那我可以这么理解吗?
var a = {n: 1};
var b = a;
a.x={n:2};
a = {n:2};
来凑个热闹 ...
解析js连续赋值的坑
@yeyi361936738 我不认同你的这个观点
你这句话恰好说明你不懂 . 的优先级大于 = 是什么意思
@onloner2012 @ruyanzhang 至于为什么不会重新解析
a
,这恰恰是说明.
操作符的优先级高于=
。因为.
操作符(a.x
)在一开始就已经被执行过,所以这时候你可以把a.x
理解成({ n: 1 }).x
。如果引擎在赋值操作(=
操作符属于二元操作符)的过程中,又去访问a
(重新解析a
),势必又会去执行.
操作符,不就说明.
操作符的优先级低于=
,这与事实(.
操作符的优先级高于=
)矛盾,所以此时=
操作符是把a.x
看作一个整体,不会重新解析a
,不过存在重新解析a
的情况:现在继续来解释下面这一段代码
由于
.
的优先级大于=
,所以首先执行的是对象属性的get
操作(又称之为左查询),通过执行该查询操作之后,发现a
对象本身以及原型链上都不存在该属性(x
);此时也意味着.
操作符的执行先告一段落。假设第二行代码里面没有=
操作符(a.x;
),到这里也就意味着,第三行代码的执行过程全部结束。然后引擎继续从x
的位置开始向右执行(可能你会问我为什么是向右执行,这是因为不同操作符的执行顺序由该操作符本身的优先级决定的,自然执行完.
操作符,然后引擎去执行=
,恰好=
操作符出现在.
操作符的右边),然后在执行的过程中遇到=
操作符,由于=
操作符具有右结合性,也就意味着这时候会首先执行=
右边的表达式,所以在上述表达式的计算过程结束后,得到计算值undefined
,并且将该计算值(undefined
)赋给a.x
。可能你会问我为什么是赋值?这是因为=
操作的作用是赋值以及该操作符是二元操作符。正如之前所提到的,{ n: 1 }
或者b
对象上没有x
属性,这也就意味着此时的赋值操作是set
操作,所以最后的结果({ n: 1 }
或者b
对象上新增一个属性值为undefined
的属性x
)也正如你所看到的那样。顺便说一句,往对象上新增一个属性或者修改已存在属性的属性值,不一定能成功。以下是一些
【进阶1-3期】JavaScript深入之内存空间详细图解
进阶系列中有的
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 }
答案如上
注意点:
1: 点的优先级大于等号的优先级
2: 对象以指针的形式进行存储,每个新对象都是一份新的存储地址
运算符优先级还真没注意,涨姿势了
以前有做过一个一样的题,等号运算符和
.
运算符优先级的问题。https://youzixr.github.io/2019/03/05/JS-%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95/
把 a.x = a = {n: 2}, 换成 b.x = a = {n: 2} 的时候,是不是会好理解了,虽然确实是这样。
undefined
{n: 2}
具体答案分析和扩展之前写过一篇类似的
https://juejin.im/post/5b605473e51d45191a0d81d8
结果:
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}。
上面是之前写的解释,最近看周爱民老师的文章的时候,发觉这部分解释有不少地方没说到本质上,有的还是错误的,所以我重新结合老师的文章研究了一下,修改如下:
以这段代码为例: