性能优化

数组优化建议

避免创建 holes,因为 V8 引擎会对数组做元素类型(elements kind)细分,数组一旦被标记为 holey 类型,则一直是 holey(至多是 HOLEY_..._ELEMENTS -> HOLEY_ELEMENTS 向下转化)。相比 packed 类型数组,对 holey 类型数组的操作需要在原型链上进行额外的检查和高代价的查找。

// 推荐字面量或空数组写法 // 字面量 const arr = [1] // 空数组 const arr = [] arr.push(1) // 创建 holes,导致 holey 类型的两个场景 const arr = new Array(2) const arr = [1] arr[3] = 2

避免 JavaScript 引擎对数组做一些高代价的原型连查找操作,例如读取超出数组长度的索引。

for-of 和 forEach 性能已接近 for 循环,量级不大时,可以使用。

避免无意义元素类型转化,-0、NaN、Infinity 都会形成 PACKED_DOUBLE_ELEMENTS 类型数组。

对于类数组,例如 DOM,可以先一次性转化为数组。

const arr = Array.prototype.slice.call(arrayLike, 0)

内存优化建议

JavaScript 自动执行内存分配(Allocate)、释放(Release),采用垃圾回收(garbage collection)把判定哪些内存不再需要的问题转化成,哪些内存是能被其他程序访问到的。垃圾回收主要采用 mark-and-sweep 算法。

栈内存(Stack Memory)特点:存放基本数据类型和值,大小在编译时确定,分配数量固定。堆内存(Heap Memory)特点:存放对象、函数和数组,大小在运行时确定,分配数量不定。引用堆的变量也是放在栈中。

内存泄漏(memory leaks,指应用不再需要的内存,因为某些原因不能返回操作系统或可用内存池)主要原因是非期望引用。

避免意外产生全局变量,即根对象,建议使用严格模式 use strict,如果必须使用全局变量,确保使用完后赋值为 null 或重新赋基本值。

现代浏览器的垃圾回收算法已能识别和处理未移除的监听和回调,但建议还是显式处理。

避免闭包创建的作用域被不正确共享。

DOM 的异常引用也会导致泄露,例如保留子节点,子节点保持父节点引用,删除父节点,父节点依然会存在内存中。

对象优化建议

V8 使用 Hidden Class 来存储 JavaScript 对象的 meta 信息,对象属性可以分为命名属性(Named properties)和元素(Elements),命名属性和元素分别存储在不同的空间中。不同对象如果属性和顺序都相同,则共享相同 Hidden Class。命名属性又分三种:in-object、fast dictionary 和 slow dictionary,in-object 属性是直接存储在对象内部,fast dictionary 存储在 Hidden Class 的 descriptor 数组中,slow dictionary 存储在对象自身的字典中,访问速度 in-object 快于 fast dictionary,fast dictionary 快于 slow dictionary。

相同属性但不同顺序,会形成新的 Hidden Class,所以为了尽可能共享 Hidden Class,创建对象时应使属性保持相同顺序。

对象被初始化之后,对象中属性的增删会形成新的 Hidden Class,同时属性的存储位置也会发生改变。如果对象进行过多的增删属性操作,则 V8 不再更新其 Hidden Class,而是把属性的 meta 息转存在 slow dictionary 中,导致访问变慢。对象初始化定义时的属性存储在 in-object,初始化之后增加的属性则存储在其他 properties 中。所以应尽量避免在对象初始化以后再动态增删属性。

V8 对字节码的优化,也是基于函数中的对象 meta 信息不变,当被优化的函数在运行过程中发生数据类型变化时就需要放弃优化的代码,回到解释执行的过程中执行并更新类型信息。



文章参考