应避免频繁调用 function.apply,改用 bind 预绑定、call 替代、复用参数数组或 reflect.apply 等更轻量方式,以消除临时 wrapper 对象开销。

避免在频繁调用 Function.apply 时重复创建临时 wrapper 对象,核心是**绕过 apply 的隐式参数数组封装过程**,改用更轻量、可复用的调用方式。
用 bind 预绑定替代动态 apply
apply 每次调用都会将 arguments 或数组转为内部参数列表,触发一次 wrapper 构造(尤其在 V8 中表现为临时 arguments 对象或 Proxy-like 封装)。若目标函数的 this 和部分参数固定,可用 bind 提前生成一次性的绑定函数:
- 绑定后函数可直接调用,不经过 apply 路径,零临时对象开销
- 适合“固定上下文 + 固定前缀参数 + 动态剩余参数”的场景
// ❌ 频繁 apply:每次 new Array(...) + 封装
function callWithArgs(fn, ctx, args) {
return fn.apply(ctx, args);
}
// ✅ 改用预绑定:只在初始化时 bind 一次
const boundFn = originalFn.bind(ctx, arg1, arg2);
boundFn(arg3, arg4); // 直接调用,无 apply 开销
用 call 替代 apply(当参数数量已知且较少)
call 不需要数组参数,而是显式列出参数,V8 等引擎对其做了深度优化,基本不产生中间 wrapper 对象:
- 参数个数 ≤ 4 时,call 性能显著优于 apply
- 可配合解构或展开(如
fn.call(ctx, ...args)),但注意:展开操作本身在旧引擎中可能触发数组复制;现代引擎(V8 10.0+)已对小数组展开做了逃逸分析优化,基本无额外对象
缓存 arguments 或参数数组(仅限无法避免 apply 的场景)
如果必须用 apply(例如参数长度完全动态、且无法提前绑定),可复用同一数组实例,避免反复 new Array() 或 Array.from():
- 用一个全局/模块级的数组池(如
const argPool = []),push/pop 复用 - 调用前
argPool.length = 0清空,再push新参数,然后fn.apply(ctx, argPool) - 注意线程安全:前端单线程无需锁,但若在 Web Worker 中使用需谨慎
升级到现代替代方案:Reflect.apply + 静态参数结构
Reflect.apply(fn, thisArg, argsList) 语义等价于 fn.apply(thisArg, argsList),但它是标准原生方法,在多数引擎中实现更直接,且便于 JIT 编译器内联。更重要的是:
- 搭配
Array.prototype.slice.call(arguments)这类老写法应淘汰;改用Array.from(arguments)或更优的[...arguments] - 若参数来自固定结构(如对象字段),优先解构后用
call,而非先组装数组再apply











