从字节码(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 

所以,上面的字节码对应代码意义上是这样

Continuous Assignment

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。

合理推测,对于堆上同一对象的引用地址,始终会从最后一个寄存器里取。

#bytecode

SideEffect is a blog for front-end web development.
Code by Axiu / rss