夜下客

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

第24章 V8高效扩展元素

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.修

『加入书签,方便阅读』