为何用箭头函数访问JavaScript数组比方括号更快?
这个现象的核心原因在于Chrome的V8引擎对箭头函数和变量捕获的特定优化策略,结合TypedArray的底层访问机制导致的,具体可以拆解为以下几点:
1. V8对箭头函数的内联与常量捕获优化
你的代码中lambda是捕获了模块级const变量array的箭头函数,V8引擎会识别到:
array是不可重新赋值的常量(const声明),箭头函数的逻辑完全固定,没有动态变化的可能;- 箭头函数的体非常简单,仅返回
array[i],符合V8的函数内联优化条件。
引擎会直接把lambda(i)的调用替换为array[i]的直接访问,但关键在于:这个内联过程会提前固化array的类型信息——引擎会确认array是Float32Array类型,并且不会被修改,因此在循环的热路径中,跳过了重复的类型检查和边界验证步骤。
而直接写array[i]时,虽然看起来更直接,但V8在某些情况下会保留对array类型的动态检查(即使你用了const,引擎仍会考虑极端场景下的原型链修改等情况,虽然你的代码里没有,但优化策略的保守性导致了额外开销)。
2. TypedArray的方括号访问的底层差异
TypedArray的方括号访问并不是普通对象的属性访问,它对应底层的内存直接读取,但V8对这种访问的优化逻辑和通过函数内联后的访问略有不同:
- 直接访问
array[i]时,引擎需要执行完整的TypedArray索引访问流程,包括范围检查、类型转换等; - 当箭头函数被内联后,引擎会将整个访问逻辑视为一个更“静态”的操作,将类型检查提前到循环外完成,循环内仅保留内存读取的核心操作,从而减少了每一次迭代的开销。
3. Firefox的差异原因
Firefox的SpiderMonkey引擎对TypedArray的方括号访问已经做了更激进的优化,直接在循环中就固化了类型信息并跳过了冗余检查,因此箭头函数的内联优化带来的提升微乎其微(仅2%左右),这也说明不同JS引擎的优化策略存在差异。
验证补充
如果修改代码,把const array改成let array(允许重新赋值),你会发现箭头函数的速度优势会消失——因为V8无法再确定array的引用不会变化,无法进行上述的固化优化,此时箭头函数的调用开销就会显现出来。
附上基准测试代码:
const l = 1e3; const n = 512 * 512; const array = new Float32Array(n) for (let i = 0; i < n; i++) array[i] = Math.random(); array.get = function (i) { return this[i]; }; const lambda = i => array[i]; class Float32ArrayLike extends Float32Array { get(i) { return this[i]; } } const arrayLike = new Float32ArrayLike(n); for (let i = 0; i < n; i++) arrayLike[i] = array[i]; for (let r = 0; r < 2; r++) { console.log("test", r) let t0, s; t0 = performance.now(); s = 0; for (let i = 0; i < l; i++) for (let i = 0; i < n; i++) s += array[i]; console.log("array[i] ", Math.round(performance.now() - t0), "ms") t0 = performance.now(); s = 0; for (let i = 0; i < l; i++) for (let i = 0; i < n; i++) s += lambda(i); console.log("lambda(i) ", Math.round(performance.now() - t0), "ms") t0 = performance.now(); s = 0; for (let i = 0; i < l; i++) for (let i = 0; i < n; i++) s += array.get(i); console.log("array.get(i) ", Math.round(performance.now() - t0), "ms") t0 = performance.now(); s = 0; for (let i = 0; i < l; i++) for (let i = 0; i < n; i++) s += arrayLike.get(i); console.log("arrayLike.get(i)", Math.round(performance.now() - t0), "ms") }
内容的提问来源于stack exchange,提问作者Guénaël Dequeker

