文章 评论 浏览 31
有一个网站可以将 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 })
文章 0 评论 0
接受
有一个网站可以将 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 })
。第 53 题:输出以下代码的执行结果并解释为什么