改%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