从字节码(bytecode)看连续赋值的执行
2020年7月26日 • ... • ☕️ 2 min read
连续赋值是绕不过的一关,不同于this、闭包等可以直接控制台输出的内容,连续赋值的过程看不到,而且也不好拆分,更像js语言的特性。
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
console.log(a.x) // undefined
console.log(b.x) // {n: 2}
这个结果,如果只是告诉我点号操作符优先级更高,所以先赋值a.x,然后由于连续赋值从➡右至左,所以执行后面的部分。理解上还是有点……反直觉。
那么,对于这些不太好理解的特性,除了概念上的记忆,有没有更直观一点的方法来辅助呢?
chrome是基于v8引擎的,所以看v8的实现,可以找到一些端倪,比如设计文档和中间产物-字节码(bytecode)。
连续赋值
顶部连续赋值的例子,生成的字节码如下
CreateObjectLiteral [0], [0], #创建对象{n:1}
Star r0 #保存在r0:a
Star r1 #保存在r1:b
CreateObjectLiteral [1], [1], #创建对象{n:2}
Star r0 #保存在r0
StaNamedProperty r1, [2], [2] #设置r1的属性x为{n:2}LdaGlobal [3], [4]
Star r3
LdaNamedProperty r3, [4], [6]
Star r2
LdaNamedProperty r0, [2], [8]
Star r4
CallProperty1 r2, r3, r4, [10]
LdaGlobal [3], [4]
Star r3
LdaNamedProperty r3, [4], [6]
Star r2
LdaNamedProperty r1, [2], [12]
Star r4
CallProperty1 r2, r3, r4, [14]
LdaUndefined
Return
所以,上面的字节码对应代码意义上是这样
a -> [0x0001] // 假设{n: 1}地址为0x0001, {n: 2}地址为0x0002
b -> [0x0001]
a -> [0x0002]
b.x -> [0x0002]
为什么第6行是r1而不是r0呢?
把代码改一下
function assignTest() {
var a = {n: 1};
var b = a;
var c = a; var d = a; a.x = a = {n: 2};
console.log(a.x)
console.log(b.x)
}
assignTest()
对应的字节码如下
CreateObjectLiteral [0], [0], #41
Star r0
Star r1
Star r2
Star r3
CreateObjectLiteral [1], [1], #41
Star r0
StaNamedProperty r3, [2], [2]
这次指向了r3,即新增代码的变量d。
合理推测,对于堆上同一对象的引用地址,始终会从最后一个寄存器里取。