arguments破坏参数类型稳定性,导致内联缓存失效、函数不可内联、隐藏类分裂及频繁去优化;改用...args可恢复IC与TurboFan全链优化。

理解 JIT Inline Caching(内联缓存,IC)对 arguments 对象造成性能损耗的关键,在于它和 V8 的类型反馈机制、对象动态性以及优化路径之间的冲突。
arguments 破坏了参数类型的可预测性
V8 的内联缓存依赖「稳定调用模式」来缓存属性访问或函数调用的类型信息。例如,连续多次调用 foo(1, 2),IC 会记录“第 0 个参数是 number,第 1 个参数是 number”,并据此生成快速路径。但一旦函数体内访问 arguments,V8 就无法再假设参数类型和数量固定——因为 arguments 是一个动态类数组对象,其内部结构可能随每次调用变化(如稀疏索引、添加非数字属性、与 rest 参数混用等)。这导致 IC 缓存频繁失效,每次都要回退到慢速的通用查找逻辑。
arguments 阻断函数内联与 TurboFan 优化
当函数体中出现 arguments,V8 会立即将该函数标记为「不可内联」,并禁用多项激进优化:
- 禁止参数提升(parameter lifting),所有参数必须通过
arguments对象间接访问,增加内存寻址开销 - 阻止 Sparkplug 和 TurboFan 对参数做类型特化(type specialization),无法生成整数加法专用指令,只能保留泛型操作
- 使函数无法进入「fast-path」编译队列,长期停留在 Ignition 字节码解释执行层
arguments 引发隐藏类分裂与去优化
arguments 对象在 V8 中不是简单封装,而是绑定到当前执行上下文的特殊对象,其属性访问行为受调用栈深度、是否被修改、是否与 eval 共存等因素影响。例如:
- 首次调用
function f() { return arguments[0]; }→ 创建 Hidden Class A - 后续某次调用中执行
arguments.length = 0或arguments.cust = true→ 触发新 Hidden Class B - IC 原先针对 A 的缓存对 B 不适用,触发去优化(deoptimization),函数降级回解释执行
高频调用下,这种分裂和去优化反复发生,形成「热代码→优化→失效→降级→再热→再优化」的恶性循环,实际执行时间远超稳定函数。
现代替代方案能彻底绕过这些问题
使用剩余参数(...args)代替 arguments,V8 可将其识别为标准数组,配合类型稳定写法,能触发完整 IC + TurboFan 优化链:
-
function sum(...nums) { return nums.reduce((a, b) => a + b, 0); }—— 参数被推断为 number[],IC 缓存数组长度访问与索引读取 - 若始终传入数字数组,TurboFan 甚至能将
reduce内联并向量化 - 完全避免
arguments带来的上下文绑定、隐藏类污染与去优化风险


















