夜下客

繁体版 简体版
夜下客 > JS修炼法则 > 第27章 从V8中启发的JS写法

第27章 从V8中启发的JS写法

一.前言

了解一些 V8 对 JS 的编译处理,来点不一样的JS写法。

不要过度关注微优化,你要对硬件有信心,相信本文的优化技巧对代码执行效率的影响是微乎其微的。

二.数组

2-1.初始化

数组初始化有两种方式:[] 和newArray()。推荐使用[] ,相对更快。

[] 创建的是一个密集数组(无hole),使用的是连续空间存储,初始化时需付出多次扩容代价,数组操作较快。

newArray() 创建的是一个空洞数组(有hole),使用基于连续存储的特化实现,初始化时预先分配容量,数组操作较慢。

空洞数组 使用的是对象存储。

同样的数组操作,相比于密集数组,空洞数组要处理额外的空洞逻辑,即在原型链上进行额外检查和昂贵查找。

如果引擎遇到一个空洞,它不能只返回 undefined,必须查找原型链并搜索一个名称为“空洞索引”的属性,这需要花费更多时间。

更重要的是,V8的数组模式从特化到一般化的元素种类转换是不可逆的。

这意味着,如果密集数组一旦转化为空洞数组,那它底层永远是空洞数组,哪怕后续的hole全部被填充,它的数组操作仍会按照空洞数组的方式进行。

上面提到的所谓的额外的空洞逻辑,此时被填充满的空洞数组,就算数组实际没有空洞,至少付出了「检查一下是不是空洞」的成本。

2-2.避免创建空洞数组

空洞数组的元素操作真的很慢。

如果真的不知道数组的初始化值,那就使用[] 创建数组吧,最多就是付出扩容代价。

2-3.避免元素种类转换

这个建议对应的JS书写就是,应该保持数组中元素类型一致。 bad case 如下:

// bad case

const array = [1, 2, 3]; // elements kind: PACKED_SMI_ELEMENTS

// 同样的,-0、 NaN 、 Infinity 在 V8 底层都使用双精度来表示

array.push(4.56); // elements kind: PACKED_DOUBLE_ELEMENTS

array.push('x'); // elements kind: PACKED_ELEMENTS

delete array[0]; // elements kind: HOLEY_ELEMENTS

// 最后元素种类转化成了更一般的 HOLEY_ELEMENTS。

// 在上面的转化中,每向下深入一层元素类型,具备的特化优化就越少,即操作速度越慢。

在运行 JavaScript 代码时,V8 为每个数组分配一个元素种类,每种元素都有自己的一组优化方式。

数组的元素种类可在运行时改变,但元素种类的转换只能单向进行,转换不可逆,且元素种类转换只能从特定种类向更一般种类转换。

在元素种类格子中,上层代表的元素种类比下层更具体,即可实现更细粒度的优化。

越往下,其代表的元素种类越宽泛,其对象操作越慢。

为获得最佳性能,尽可能避免元素种类向下转换。

2-4.避免数组越界访问

当数组越界访问时,由于 V8 的边界检查失败,所以JS引擎一定会执行昂贵的原型链查找。

不仅仅是由于原型链查找更慢,更重要的是,数组会被V8标记「需处理特殊情况」,访问速度会更比以前慢。

因数组一旦因越界情况而被标记,它再也不会像读取越界之前那样快。

2-5.避免使用类数组对象

数组的各种操作是内置优化的,类数组对象完全不具备数组方法。

业界很常用的一个方式是,Array.prototype.Xxxx.call(arrayLike,0) 进行绑定。

Xxxx 是 slice 等数组方法。

举个代码例子,比如

const arrayLike = {

『加入书签,方便阅读』