[Leon通俗易懂]V8引擎: Hidden Class

1,140 阅读3分钟

V8引擎: Hidden Class

一、前言:

偶然看到React性能悬崖这个文章,发现还是hidden class搞出来的。想到了之前看到过的hidden class和 inline cache的文章觉得蛮有意思的。疫情的时候比较闲想复现一些浏览器内核漏洞拿来装逼,所以本地搭建了v8环境。刚好可以拿来测一下hidden class这个前端底层知识,说不定以后前端面试还可以拿出来凡尔赛哈哈哈哈

二、一个问题:

屏幕快照 2021-06-09 下午10.59.18.png

这段代码对我们前端来说,b与c的结果是一样的,毕竟前端用key取值的时候不看声明顺序。但是结果真的是这样吗?实际上并不是。带着问题先往下看

三、为什么我们需要Hidden Class

看下面这段C/C++代码

image.png

在这种编译型语言中,获取对象a的属性x的时候,实际上就是对象a的地址,大小是一个整型。获取b对象的y的时候就是b的地址加上4个字节,通过偏移量能够直接生成对应的汇编代码,而其中的”x”和”y”运行时都不再需要,因为我们不再需要额外的查找它们的属性地址(类型已经确定了,长度也确定)。偏移信息使它只要几个机器语言指令就可以存取变量、找出变量或者执行其他任务。

image.png

看下面这段Js代码

image.png

作为解释型语言,JS中这一切都是解释执行的,不管是解释器还是JIT技术,面对的难题都是类型问题。 JS在这里使用了基于哈希函数的字典式结构,与单纯的offset取值相比,很明显从最终的汇编代码上就会导致极大的性能差异。例如属性名匹配需要非常长的时间还会浪费很多内存空间。

image.png

所以为了加快对象属性和方法在内存中的查找速度, v8引擎引入了hidden class机制。 实际上引入隐藏类之后,我们可以理解成v8引擎通过这种机制在底层让每个js object维护一个自己的隐藏类。在通过key-value的方式取值时,能够通过计算隐藏类中各个属性的类型,所占的内存空间,来趋近于上面编译型语言的取值方式,以获得更优的性能。(当然v8引擎本身就是C/C++编写的,这么多优化也只是能使它更趋近与底层语言的性能,而无法超越..也算语言特性吧)

四、Hidden Class V8演示

回到最上面这个图

屏幕快照 2021-06-09 下午10.59.18.png

看一下a这个变量在底层是啥样的

屏幕快照 2021-06-09 下午10.43.33.png Debug print a后就可以看到,a变量通过map属性保存了自己的隐藏类地址,原型地址以及命名属性与元素(对v8中properties和elements如何保存快慢属性可以参考这个官方链接)。所以像我们上面说的,每个对象都会维护一个自己的hidden class。图里面下半部分就是hidden class地址对应的数据。

阿有点困了。。放一下变量b和c的数据先

b:

屏幕快照 2021-06-09 下午10.43.43.png

c:

屏幕快照 2021-06-09 下午10.43.53.png

发现了吗,a与b的隐藏类相同。而声明c之后,c创建了一个自己的隐藏类。所以实际上这里发生了两件事。

  1. 当我们声明let a = {a: 1, b: 2}时,虽然在js中是一个语句,但是在V8 parser解析成ast之后, 这段代码可以被解析成两个语句let a = {a: 1}; a.b = 2;
  2. 当a与b声明key的顺序相同时,两者共用同一个hidden class,a与c不同,所以c不能复用a与b的class,v8会创建一个新的hidden class让c指向。

五、So

总是按照相同的key顺序初始化或者创建js object,就能够少创建hidden class,就能优化一点点点点点点的内存空间,就能更好的装杯啦(不是