一文详解JavaScript中 call、apply、bind
作者:yqcoder
引言
在 JavaScript 开发中,this 的指向问题一直是初学者的痛点,而 call、apply 和 bind 则是解决这一问题的三把“钥匙”。它们都属于 Function.prototype 上的方法,核心作用都是改变函数执行时的上下文(即 this 指向)。
虽然目的相同,但它们的用法和返回结果各有不同。本文将带你从原理到实战,一次性掌握它们。
1. 核心概念:什么是“借用”?
想象一下,你有一个工具箱(对象 A),里面有一把锤子(方法)。现在你想用这把锤子去敲钉子,但你不想把整个工具箱搬过来,或者你当前所在的场景(对象 B)没有锤子。
call/apply:就像是把锤子借过来,立刻敲一下钉子。用完就还回去。bind:就像是把锤子绑定在你的腰带上。以后每次你需要敲钉子时,拿出来的都是这把特定的锤子,而且可以稍后再使用。
2. 语法与区别速览
| 特性 | call | apply | bind |
|---|---|---|---|
| 第一个参数 | thisArg (新的 this 指向) | thisArg (新的 this 指向) | thisArg (新的 this 指向) |
| 后续参数 | 逐个传递 (arg1, arg2, ...) | 数组形式传递 ([arg1, arg2, ...]) | 逐个传递 (arg1, arg2, ...) |
| 返回值 | 立即执行原函数,返回原函数的返回值 | 立即执行原函数,返回原函数的返回值 | 返回一个新函数,不立即执行 |
| 主要用途 | 继承、调用对象方法 | 数组操作(如求最大值)、合并数组 | 回调函数、保持 this 指向、柯里化 |
3. 深度解析与代码示例
为了演示,我们先定义两个对象和一个通用函数:
const person = {
name: "Alice",
age: 25,
};
const dog = {
name: "Buddy",
age: 3,
};
function introduce(greeting, punctuation) {
// 这里的 this 指向调用时的上下文
console.log(
`${greeting}, I am ${this.name}, ${this.age} years old${punctuation}`,
);
}
3.1 Function.prototype.call()
call 方法接受一个 this 指向作为第一个参数,后面的参数逐个传入。
特点:参数列表形式,直观清晰。
// 将 introduce 函数的 this 指向 person 对象 introduce.call(person, "Hello", "!"); // 输出: Hello, I am Alice, 25 years old! // 将 introduce 函数的 this 指向 dog 对象 introduce.call(dog, "Woof", "."); // 输出: Woof, I am Buddy, 3 years old.
经典应用场景:实现继承
function Animal(name) {
this.name = name;
}
function Cat(name, color) {
// 借用 Animal 的构造函数,将 this 指向 Cat 的实例
Animal.call(this, name);
this.color = color;
}
const myCat = new Cat("Kitty", "white");
console.log(myCat.name); // 'Kitty'
console.log(myCat.color); // 'white'
3.2 Function.prototype.apply()
apply 方法与 call 非常相似,唯一的区别在于第二个参数必须是一个数组(或类数组对象)。
特点:适合处理数组参数。
// 参数以数组形式传递 introduce.apply(person, ["Hi", "?"]); // 输出: Hi, I am Alice, 25 years old?
经典应用场景:数学运算
在没有 ES6 展开运算符 (...) 之前,apply 常用于将数组传递给不接受数组参数的函数。
const numbers = [5, 6, 2, 3, 7]; // Math.max 不接受数组,只接受逐个参数 // 使用 apply 将数组展开为参数列表 const max = Math.max.apply(null, numbers); console.log(max); // 7 // 注:现代 JS 更推荐写法: Math.max(...numbers)
3.3 Function.prototype.bind()
bind 方法不会立即执行函数,而是返回一个新的函数。这个新函数的 this 被永久绑定到了指定的对象上。
特点:返回新函数,可延迟执行,this 指向不可再次修改。
// 创建一个新函数,this 永久绑定到 person
const boundIntroduce = introduce.bind(person, "Hey");
// 稍后执行
boundIntroduce("!");
// 输出: Hey, I am Alice, 25 years old!
// 即使尝试再次改变 this,也不会生效
boundIntroduce.call(dog, "?");
// 输出依然是: Hey, I am Alice, 25 years old! (this 仍然是 person)
经典应用场景:setTimeout 中的 this 丢失
在事件回调或定时器中,this 往往会指向 window 或 undefined,使用 bind 可以锁定上下文。
const button = {
label: "Click Me",
clickHandler: function () {
console.log(`Button ${this.label} clicked`);
},
};
// 错误示范:this 指向 window
// setTimeout(button.clickHandler, 1000);
// 正确示范:绑定 this 到 button 对象
setTimeout(button.clickHandler.bind(button), 1000);
// 1秒后输出: Button Click Me clicked
4. 面试高频考点总结
call 和 apply 的区别?
- 接收参数的方式不同。
call是逐个参数,apply是数组参数。除此之外,功能完全一致。
bind 和 call/apply 的区别?
call/apply是立即执行函数。bind是返回一个新函数,需要手动调用才会执行。
bind 返回的函数能再次被 bind 吗?
- 不能。一旦
bind绑定了this,后续无论再用call、apply还是bind都无法改变该函数的this指向。
如果第一个参数传 null 或 undefined 会发生什么?
- 在非严格模式下,
this会指向全局对象(浏览器中是window)。 - 在严格模式 (
'use strict') 下,this保持为null或undefined。
5. 手写简易版 Polyfill(进阶)
理解原理的最好方式就是自己实现一个。以下是 call 的简易实现思路:
Function.prototype.myCall = function (context, ...args) {
// 1. 判断调用者是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 2. 处理 context,如果是 null/undefined 则指向 window/globalThis
context = context || globalThis;
// 3. 将当前函数挂载到 context 上,作为其方法
// 使用 Symbol 避免属性名冲突
const fnKey = Symbol("fn");
context[fnKey] = this;
// 4. 执行该函数,并保存结果
const result = context[fnKey](...args);
// 5. 删除临时添加的属性
delete context[fnKey];
// 6. 返回结果
return result;
};
结语
call、apply 和 bind 是 JavaScript 灵活性的体现。掌握它们,不仅能让你更好地控制 this 指向,还能写出更优雅、复用性更高的代码。
- 需要立即执行且参数少?用
call。 - 需要立即执行且参数在数组里?用
apply。 - 需要延迟执行或固定上下文?用
bind。
希望这篇文章能帮你彻底攻克这三个方法!
以上就是一文详解JavaScript中 call、apply、bind的详细内容,更多关于JavaScript中call、apply、bind详解的资料请关注脚本之家其它相关文章!
