apply的核心机制是立即调用函数并指定this值,第二个参数必须是数组或含length属性的类数组对象;传入普通对象、字符串或null会报错或行为异常。

apply 改变 this 指向的核心机制
apply 的本质是立即调用函数,并把第一个参数设为该次调用的 this 值,第二个参数必须是数组(或类数组对象)。它不创建新函数,也不缓存绑定,只做一次调用。关键点在于:第二个参数必须可被展开为独立实参,且类型校验严格——传字符串、null 或普通对象会直接报错。
正确传参:必须是数组或 arguments 这类类数组
常见错误是误传对象字面量或字符串,比如 fn.apply(obj, {0: 'a', 1: 'b'}) 看似像数组,但不是 Array 实例且缺少 length 属性,apply 会静默忽略后续参数;传 'ab' 则每个字符被当一个参数,结果完全偏离预期。
- ✅ 正确:
fn.apply(ctx, ['x', 'y'])、fn.apply(ctx, Array.from(arguments)) - ✅ 类数组也行:
fn.apply(ctx, {0: 'a', 1: 'b', length: 2})(必须有length) - ❌ 错误:
fn.apply(ctx, {a: 'x', b: 'y'})、fn.apply(ctx, 'xy')、fn.apply(ctx, null)
和 call、bind 的关键区别在哪
apply 和 call 都是立即执行,区别仅在参数形式:call 后接逗号分隔的参数列表,apply 强制要求第二个参数是数组结构。而 bind 返回新函数,不执行,且其参数是“预置前缀”,后续调用还能再追加参数。性能上三者无差异,但语义不同:需要动态构造参数列表时(比如从 arguments 转发),apply 是唯一自然选择。
- 转发不定参数:
function wrapper() { return target.apply(this, arguments); } - 不能写成
target.call(this, arguments)—— 这会把整个arguments当作第一个参数传进去 -
bind不适用此场景,因为它不立即执行,也无法“解包”数组到实参
现代替代方案:扩展运算符更直观
ES6+ 中,fn(...args) 已能替代大部分 apply 场景,尤其当你已有数组变量时。但它无法指定 this,所以仍需 apply 或 call 配合。真正容易被忽略的是:箭头函数没有自己的 this,用 apply 试图改变它的 this 是无效的——它始终继承外层作用域的 this,这点和普通函数完全不同。
- 等价写法:
fn.apply(ctx, arr)≡fn.call(ctx, ...arr) - 但
call+ 扩展运算符在参数过多时可能触发栈溢出(V8 限制约 10 万),apply更稳妥 - 箭头函数里写
fn.apply(customThis, [...])有效,但this绑定发生在定义时,不是调用时
真正要注意的是类数组对象的 length 属性是否准确,以及箭头函数对 this 的不可变性——这两处不看文档很容易掉坑里。










