JavaScript中call, apply, bind 的作用和区别
call,apply,bind都能改变this指向。call和apply立即执行,仅参数形式不同。bind不立即执行,而是返回一个新函数。
我们来详细解析一下 JavaScript 中的 call, apply, 和 bind。这三者是 JS 中非常重要的概念,尤其在处理函数上下文 this 的指向时。
核心共同点:改变 this 指向
call, apply, bind 这三个函数都是 Function.prototype 上的方法,这意味着任何函数都可以调用它们。
它们最核心的作用都是一样的:改变函数执行时的 this 上下文。
我们先从一个简单的例子开始,理解为什么需要改变 this:
javascript
const person = {
name: '张三',
sayHello: function() {
console.log(`你好,我是 ${this.name}`);
}
};
const anotherPerson = {
name: '李四'
};
person.sayHello(); // 输出: 你好,我是 张三
// 如果我们想让 anotherPerson 也“借用” sayHello 这个方法,直接调用会出问题
// const say = person.sayHello;
// say(); // 在非严格模式下,this 指向 window,输出 "你好,我是 "
call, apply, bind 就是为了解决这类问题而生的,它们可以显式地指定 sayHello 函数在执行时,内部的 this 应该指向谁。
1. call()
- 作用:调用一个函数,并将其
this值设置为提供的对象。同时,可以逐个传入参数。 - 特点:立即执行函数。
- 语法:
function.call(thisArg, arg1, arg2, ...)
示例:
javascript
const person = {
name: '张三'
};
function sayHello(greeting, punctuation) {
console.log(`${greeting},我是 ${this.name}${punctuation}`);
}
// 使用 call 来调用 sayHello,并指定 this 为 person 对象
// 后面的参数 '你好' 和 '!' 会依次传递给 sayHello 函数
sayHello.call(person, '你好', '!'); // 输出: 你好,我是 张三!
2. apply()
- 作用:调用一个函数,并将其
this值设置为提供的对象。同时,参数必须以一个数组(或类数组对象)的形式传入。 - 特点:立即执行函数。
- 语法:
function.apply(thisArg, [argsArray])
示例:
apply 和 call 的作用几乎完全一样,唯一的区别就是传参的方式。
javascript
const person = {
name: '张三'
};
function sayHello(greeting, punctuation) {
console.log(`${greeting},我是 ${this.name}${punctuation}`);
}
// 使用 apply 来调用 sayHello,并指定 this 为 person 对象
// 后面的参数以数组形式 ['你好', '!'] 传递
sayHello.apply(person, ['你好', '!']); // 输出: 你好,我是 张三!
apply 的一个经典妙用:
利用 apply 参数是数组的特性,可以方便地操作数组,例如求数组中的最大/最小值。
javascript
const numbers = [5, 6, 2, 3, 7];
// Math.max 本身不接受数组作为参数,它接受的是一串数字 Math.max(arg1, arg2, ...)
// 使用 apply 可以巧妙地将数组展开并作为参数传入
const max = Math.max.apply(null, numbers); // null 表示 this 不重要
const min = Math.min.apply(null, numbers);
console.log(max); // 7
console.log(min); // 2
// (在 ES6 中,可以使用扩展运算符更方便地实现: Math.max(...numbers))
3. bind()
- 作用:创建一个新函数。这个新函数的
this会被永久地绑定到bind()的第一个参数。bind()也可以预先设置部分参数。 - 特点:不立即执行,而是返回一个绑定了
this的新函数。 - 语法:
const newFunction = function.bind(thisArg, arg1, arg2, ...)
示例:
javascript
const person = {
name: '张三'
};
function sayHello(greeting, punctuation) {
console.log(`${greeting},我是 ${this.name}${punctuation}`);
}
// 使用 bind 创建一个新函数 greetZhangSan
// 这个新函数的 this 永远指向 person 对象
const greetZhangSan = sayHello.bind(person, '你好');
// bind 不会立即执行,我们需要手动调用返回的新函数
greetZhangSan('。'); // 输出: 你好,我是 张三。
const greetZhangSanAgain = sayHello.bind(person);
greetZhangSanAgain('嗨', '!'); // 输出: 嗨,我是 张三!
bind 在异步场景(如事件监听、setTimeout)中非常有用,因为这些场景下 this 很容易丢失。
bind 的经典应用场景:
javascript
class Logger {
constructor() {
this.count = 0;
}
logMessage() {
// 这里的 this 期望指向 Logger 的实例
console.log('点击次数:', ++this.count);
}
}
const logger = new Logger();
// 错误的做法:
// 当 click 事件触发时,logMessage 内部的 this 会指向 button 元素本身,而不是 logger 实例
// document.querySelector('button').addEventListener('click', logger.logMessage);
// 这会导致 this.count 是 undefined
// 正确的做法:
// 使用 bind 将 logMessage 的 this 永久绑定到 logger 实例上
const boundLogMessage = logger.logMessage.bind(logger);
document.querySelector('button').addEventListener('click', boundLogMessage);
// 每次点击按钮,都会正确输出: 点击次数: 1, 点击次数: 2, ...
核心区别总结
| 特性 | call() |
apply() |
bind() |
|---|---|---|---|
| 执行时机 | 立即执行 | 立即执行 | 返回新函数,不立即执行 |
| 参数传递 | 逐个传递参数 | 以数组或类数组对象的形式传递 | 逐个传递参数(可以预设部分参数,也叫柯里化) |
| 返回值 | 函数的执行结果 | 函数的执行结果 | 一个绑定了 this 和预设参数的新函数 |
| 核心记忆 | Call for Comma (逗号分隔参数) | Apply for Array (数组参数) | Bind for Bound function (返回绑定函数) |
总结
- 如果你想立即调用一个函数并改变其
this指向,并且参数是逐个传入的,使用call。 - 如果你想立即调用一个函数并改变其
this指向,并且参数是以数组形式传入的,使用apply。 - 如果你想创建一个新函数,使其
this永久指向某个对象,并且稍后再调用它(例如用于回调函数、事件处理),使用bind。
右滑查看面试常问