ratorResult.done) break;
result.push(iteratorResult.value);
return result;
这段代码比clone慢,原因如下:
它需要创建一个iterator,并在创建前加载和获取Symbol.iterator属性
每次循环,它都需要创建一个iteratorResult对象,并进行属性访问
每次循环,push都会触发result的扩容操作
这里的性能瓶颈不再分析,感兴趣的自行阅读:属性-002 V8如何处理JS属性,数组扩容-001 由数组切入V8元素种类
V8之所以采用这种处理方式,是因为...不仅仅可以用于数组,它可以用于一切可迭代对象,哪怕你根据迭代器协议自定义一个对象。
// 自定义一个遵循 迭代器协议 的JS对象
const myIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
},
};
console.log([...myIterable]); // [1, 2, 3]
尽管如此,但实际上在底层,V8可以识别当前扩展元素是否为数组。比如:
避免创建迭代器对象,比如iterator
避免创建迭代器结果对象,比如iteratorResult
最好提前知道数组长度以分配内存,避免数组扩容
V8团队将上述思路在快数组中进行了实践。快数组是V8六种常见数组种类中的一种。
当...用在数组开头时,形如[...arr],测试用例数组长度为 1000000 ,这种新思路产生了大约3倍的性能提升,比自定义clone快了大约25%。
所以我们在JS开发时,应尽量使用这种写法。
这种优化也适用于[...arr,1,2]。但是不适用于[1,2,...arr]。
四.谨慎使用高效扩展
高效扩展指的是V8内部实现的代码思路,使用了上述提及的新思路。
上述的性能提升令人亢奋,但我们必须小心使用以保证高效扩展真的生效,因为JS允许我们随意修改对象的迭代行为。
当扩展元素使用自定义迭代器协议时,我们需确保修改是OK的。
毕竟当原始迭代器接口被改变时,高效扩展有可能会完全失效。比如下面的几种场景。
4-1.赋值Symbol.iterator属性
一般情况下,数组没有自己的Symbol.iterator属性,因此访问该属性时,是在Array.prototye上找到的。
在下面代码中,通过自身自定义Symbol.iterator属性来绕过原型。arr修改之后,访问Symbol.iterator得到一个空迭代器,因此扩展操作返回空,最后result是一个空数组。
const arr = [1, 2, 3];
arr[Symbol.iterator] = function() {
return { next: function() { return { done: true }; } };
};
const result = [...arr];
// → []
const array = [1, 2, 3];
const res1 = [...array];
// → [1, 2, 3]
Array.prototype[Symbol.iterator] = function() {
return { next: function() { return { done: true }; } };
};
const res2 = [...array];
// → []
4-2.修