【JavaScript 深入】call、apply、bind 全解析与历史背景

24 阅读3分钟

🔹 1. 共同点

  • 都定义在 Function.prototype 上。

  • 都可以指定函数运行时的 this

  • 都支持传参。

换句话说,它们都是为了 改变函数调用时的上下文 而设计的。


🔹 2. 区别对比

方法语法参数形式是否立即执行返回值
callfn.call(thisArg, arg1, arg2, …)参数列表(逐个传)✅ 是函数执行结果
applyfn.apply(thisArg, [argsArray])数组或类数组(一次性传入)✅ 是函数执行结果
bindfn.bind(thisArg, arg1, arg2, …)参数列表(可预设一部分)❌ 否返回新函数(this 已绑定)

🔹 3. 示例代码

function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const user = { name: "Alice" };

// call:参数逐个传
greet.call(user, "Hello", "!");  
// => Hello, Alice!

// apply:参数打包成数组
greet.apply(user, ["Hi", "!!!"]);  
// => Hi, Alice!!!

// bind:不会立即执行,返回一个新函数
const bound = greet.bind(user, "Hey");
bound("?");  
// => Hey, Alice?

🔹 4. bind 的特殊点

  1. 惰性执行:不会立即调用,必须手动执行返回的新函数。

  2. 参数柯里化:第一次绑定时可传一部分参数,调用时继续传。

  3. 构造函数场景:用 new 调用时,this 会指向新对象,忽略 bind 的 this。

function Person(name) {
  this.name = name;
}

const BoundPerson = Person.bind({ x: 1 });
const p = new BoundPerson("Alice");
console.log(p.name); // Alice

这里 new 的优先级更高,覆盖了 bind 绑定的 this。


🔹 5. 记忆技巧

  • call:C → Comma(逗号传参,一个个列出来)。

  • apply:A → Array(数组传参)。

  • bind:B → Bind & return(绑定并返回新函数,不执行)。


🔹 6. 历史背景:为什么有 call 和 apply?

很多人会疑惑:call 和 apply 区别不大,为什么要设计两个方法?

(1)ES3 时代的限制

1999 年 ES3 发布时:

  • 没有扩展运算符 ...。

  • 没有解构语法。

  • 甚至还没有 bind。

如果要把数组作为参数传给函数,写法非常笨拙:

function sum(a, b, c) {
  return a + b + c;
}

const arr = [1, 2, 3];

// 没有 ...arr 只能手动展开
sum(arr[0], arr[1], arr[2]);

为了解决这个问题,apply 应运而生:

sum.apply(null, [1, 2, 3]); // ✅ 直接传数组

而 call 则是参数列表形式的补充:

sum.call(null, 1, 2, 3);

两者语义清晰,互为补充。


(2)ES5 之后的新变化

2009 年 ES5 引入了 bind,可以返回一个预绑定 this 的函数。

2015 年 ES6 增加了扩展运算符 ...,于是以前只能用 apply 的写法,现在可以用 call 替代:

// 以前必须 apply
Math.max.apply(null, [1, 2, 3]);

// ES6 之后
Math.max.call(null, ...[1, 2, 3]);

因此在现代语法中,apply 的存在感逐渐降低。


(3)为什么仍然保留 apply?

  1. 历史包袱:大量旧代码依赖 apply,无法移除。

  2. 语义清晰:一眼看到 apply,就知道参数是数组。

  3. 规范稳定性:JavaScript 倡导“不废弃 API”,保持向后兼容。


🔹 7. 总结

  • call 和 apply 功能几乎一样,唯一区别是参数传递形式。

  • 在 ES3/ES5 时代,这种设计很有必要,因为没有展开语法。

  • ES6 之后 apply 的存在感降低,但仍保留作为历史兼容和语义工具。

  • bind 则是更“现代”的 API,解决惰性执行和函数柯里化的问题。

👉 可以说,call 与 apply 是 JavaScript 早期语法限制下的“双胞胎产物”,而 bind 是后期补充的新成员。