夜下客

繁体版 简体版
夜下客 > JS修炼法则 > 第24章 V8高效扩展元素

第24章 V8高效扩展元素

改%ArrayIteratorPrototype%

通过修改 %ArrayIteratorPrototype% 可直接改变next方法。

%ArrayIteratorPrototype%是数组迭代器的原型,它作用于所有的数组。

Object.getPrototypeOf([][Symbol.iterator]()).next = function() {

return { done: true };

const arr = [1, 2, 3];

const result = [...arr];

// → []

4-3.扩展空洞数组

浅克隆空洞数组时要小心。不过你可以放心,...可以处理这种默认情况下的空洞。它不会盲目复制数组后续的元素,它会留意到空洞,并小心地用undefined填充。

因为...遵循迭代器原则,因此在扩展时不会保留空洞,而是用数组原型中对应索引处的值填充空洞。

默认情况下,数组原型中没有任何元素,这意味着所有空洞默认用undefined填充。

const arr = [0,1,,,4,,6]

const res1 = [...arr]

// → [0, 1, undefined, undefined, 4, undefined, 6]

Array.prototype[2] = 222

Array.prototype[3] = 333

Array.prototype[5] = 555

const res2 = [...arr]

// → [0, 1, 222, 333, 4, 555, 6]

做个简单测试,在长度为100,000的数组中,仅填充 600 个整数,剩下的都是空洞。采用了新设计思路的...比clone快4倍以上。

注意: 虽然上图包含slice,但这种比较其实并不公平。因为...和clone发生了复制操作,而slice不处理空洞,它对所有空洞进行保留,因此它的工作量要少很多。

高效扩展中用undefined填充空洞并不像听上去这么简单。因为它可能需要将数组转换成另一种元素种类。

下图描述了这种场景。设置和上述相同,只是将 600 个整数换成了未装箱的双精度数字,因此这个数组的元素种类是HOLEY_DOUBLE_ELEMENTS。

因为元素种类不能像undefined这种被标记存储,所以扩展涉及了代价昂贵的元素种类转换。

这就是为什么同样是[...arr], 但双精度数字arr的得分比上一个图低很多。但它仍比clone快。

4-4.扩展字符串、set、map

跳过迭代器对象、避免结果数组扩容的思想同样适用于扩展其他数据类型。

事实上,V8已经为原始字符串、set、map都实现了高效扩展。

对于set而言,高效扩展不仅局限于直接扩展[...set],也支持键扩展[...set.keys()]和值扩展[...set.values()]。在V8团队的内部测试中,新实践比之前快了大约18倍。

高效扩展在map中也有类似实现。但并未在直接扩展[...map]中实现,因为V8团队认为这种操作并不常见。出于相同考虑,entries()迭代器也未能采用高效扩展思路去实现。在V8团队的内部测试中,新实践比之前快了大约14倍。

对于[...string]而言,性能大约提升了5倍,用紫色和绿色线表示。它甚至比 TurboFan 优化的 for-of-loop 代码更快(TurboFan 理解字符串迭代器并为其生成优化代码),用蓝色和粉色线表示。V8团队分别对一字节字符串、两字节字符串进行测试。

五.提升Array.from性能

幸运的是,当Array.from被一个迭代器对象调用并且没有使用映射函数时,它可以复用扩展元素的高效实现方式。满足场景比如Array.from([1,2

『加入书签,方便阅读』