为何for...of循环比传统for循环慢很多?
for-of循环比传统索引for循环慢近70%? 我搭建了一个简单的基准测试,对比for (const x of arr)和for (let i = 0; i < arr.length; i++)循环的性能,测试用的是包含10000个随机数的数组:
初始化代码
const arr = [...new Array(10000)].map(() => Math.random());
测试用例1:传统索引for循环
let sum = 0; for (let i = 0; i < arr.length; i++) { sum += arr[i]; }
测试用例2:for-of循环
let sum = 0; for (const x of arr) { sum += x; }
多次重复测试后,结果始终显示for-of循环比传统索引for循环慢近70%(约4.3万次/秒 vs 约15万次/秒)。我已经排除了微基准测试的常见问题(使用大数组),且借助测试框架避免了冷启动影响,想知道这种性能差距的核心原因。
核心原因分析
迭代器协议的额外开销:
for-of基于ES6迭代器协议工作,每次循环前需要调用数组的Symbol.iterator方法生成迭代器对象,之后每一轮循环都要调用迭代器的next()方法,每次调用都会返回一个包含value和done属性的对象。这些函数调用和对象创建的开销,在大数组循环时会被显著放大。而传统索引for循环只是简单的索引递增+数组元素访问,没有这些额外的抽象层开销。引擎优化的差异:传统索引for循环是JavaScript引擎最容易优化的循环模式——固定的起始/终止条件、线性的索引递增逻辑,引擎可以做大量激进优化:比如自动缓存数组长度、消除数组访问的边界检查、甚至通过向量化指令把循环逻辑批量执行。而
for-of的迭代器机制逻辑更复杂,引擎很难对其做同等程度的优化,尤其是普通数组的迭代器包装逻辑无法被完全消除。变量绑定的额外操作:
for (const x of arr)在每一轮循环都会创建一个新的块级变量绑定,相比传统for循环直接复用索引变量i、通过索引访问元素的方式,多了一层变量绑定的开销。
内容的提问来源于stack exchange,提问作者Gaurang Tandon

