JavaScript 中的 call、apply 与 bind 方法
七崽爱吃小饼干2025/12/30阅读 5专栏 JavaScript
一、核心共性
call、apply、bind 这三个方法均为 Function 原型上的方法(所有函数都可以直接调用),它们的核心作用完全一致:手动改变(绑定)函数执行时的 this 指向,让函数可以在指定的上下文环境中执行。
二、各自特性与用法
1. call 方法
- 执行特性:调用
call后,函数会立即执行。 - 参数传递:第一个参数是要绑定的
this指向(若传递 null/undefined,在非严格模式下this会指向全局对象:浏览器中是window,Node.js 中是global);从第二个参数开始,是单个的、依次传递的参数列表(即逐个传入函数所需的参数)。 - 语法格式:
函数名.call(thisArg, arg1, arg2, ..., argN) - 代码示例:
javascript
// 定义一个函数
function sayHello(greet, punctuation) {
console.log(`${greet},我是${this.name}${punctuation}`);
}
// 定义上下文对象
const person = { name: "张三" };
// 使用 call 绑定 this 并立即执行,逐个传递参数
sayHello.call(person, "你好", "!"); // 输出:你好,我是张三!
sayHello.call(null, "哈喽", "~"); // 非严格模式下 this 指向 window,若 window 无 name 属性则输出:哈喽,我是undefined~
2. apply 方法
- 执行特性:与
call一致,调用后函数会立即执行。 - 参数传递:第一个参数同样是要绑定的
this指向;第二个参数是一个数组(或类数组对象),数组中的每一项对应函数的一个参数(无需逐个拆分参数,适合参数数量不固定或已存在于数组中的场景)。 - 语法格式:
函数名.apply(thisArg, [arg1, arg2, ..., argN]) - 代码示例:
javascript
// 复用上面的 sayHello 函数和 person 对象
const args = ["早上好", "。"];
// 使用 apply 绑定 this 并立即执行,数组传递参数
sayHello.apply(person, args); // 输出:早上好,我是张三。
// 也可以直接传递字面量数组
sayHello.apply(person, ["晚上好", "!"]); // 输出:晚上好,我是张三!
3. bind 方法
- 执行特性:与 call、apply 不同,bind 调用后不会立即执行函数,而是返回一个永久绑定了该
this指向的新函数(后续调用这个新函数时,this不会再改变,除非使用 call/apply 再次修改)。 - 参数传递:第一个参数是要绑定的
this指向;从第二个参数开始,可以预先传递部分参数(柯里化),后续调用新函数时,只需传递剩余参数即可。 - 语法格式:
const 新函数 = 函数名.bind(thisArg, arg1, arg2, ..., argN) - 代码示例:
javascript
// 复用上面的 sayHello 函数和 person 对象
// 使用 bind 绑定 this,预先传递第一个参数 "嗨",返回新函数
const bindSayHello = sayHello.bind(person, "嗨");
// 后续调用新函数,只需传递剩余参数
bindSayHello("!"); // 输出:嗨,我是张三!
bindSayHello("~"); // 输出:嗨,我是张三~
// 新函数的 this 已固定,无法通过普通调用改变
const otherPerson = { name: "李四" };
bindSayHello.call(otherPerson, "?"); // 仍输出:嗨,我是张三?(this 未改变)
三、核心区别总结
| 特性 | call | apply | bind |
|---|---|---|---|
| 函数执行时机 | 立即执行 | 立即执行 | 不立即执行,返回新函数 |
| 参数传递方式 | 逐个传递参数列表 | 传递参数数组/类数组 | 逐个传递(支持预先传参) |
| 返回值 | 函数执行后的返回值 | 函数执行后的返回值 | 绑定 this 后的新函数 |
四、典型使用场景
你想了解 call、apply、bind 三个方法的实际业务使用场景,它们各自有明确的适用场景,下面结合具体可运行的代码示例详细说明:
1.call 方法的实际使用场景
call 适合参数数量明确、需要立即执行函数,且常用来「借用方法」或「统一函数执行上下文」的场景。
构造函数继承(辅助实现属性继承)
在 ES6 class 出现之前,通过构造函数实现继承时,可使用 call 调用父构造函数,实现子实例继承父构造函数的属性。
javascript
// 父构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 子构造函数
function Student(name, age, score) {
// 借用 Person 构造函数,将 this 绑定为 Student 实例,继承 name 和 age 属性
Person.call(this, name, age);
this.score = score; // 子实例自身属性
}
const student1 = new Student("小明", 18, 95);
console.log(student1); // Student { name: "小明", age: 18, score: 95 }
2. apply 方法的实际使用场景
apply 最核心的优势是支持以数组/类数组传递参数,适合参数已存在于数组中、或参数数量不固定的场景,且函数需要立即执行。
求数组的最大值/最小值(解决 Math 方法不支持数组参数问题)
Math.max() 和 Math.min() 只支持逐个传入参数,不支持直接传入数组,通过 apply 可快速传递数组参数实现需求。
javascript
// 数组求最大值
const nums1 = [12, 45, 8, 23, 56, 9];
const maxNum = Math.max.apply(null, nums1); // 等价于 Math.max(12,45,8,23,56,9)
console.log("数组最大值:", maxNum); // 56
// 数组求最小值
const nums2 = [3, 7, 1, 9, 4, 6];
const minNum = Math.min.apply(null, nums2); // 等价于 Math.min(3,7,1,9,4,6)
console.log("数组最小值:", minNum); // 1
// 补充:ES6 扩展运算符(...)可替代此场景,但 apply 是传统兼容方案
const maxNumES6 = Math.max(...nums1);
console.log(maxNumES6); // 56(效果一致)
三、bind 方法的实际使用场景
bind 的核心特性是不立即执行函数,返回一个永久绑定 this 指向的新函数,适合需要延迟执行、且需要固定 this 上下文的场景。
场景1:事件处理函数中固定 this 指向
DOM 事件触发时,事件处理函数的 this 默认指向触发事件的 DOM 元素,若需要让 this 指向自定义对象,可通过 bind 绑定。
javascript
// 自定义上下文对象
const user = {
username: "小红",
sayHello: function() {
console.log(`${this.username} 点击了按钮`);
}
};
// 获取按钮元素
const btn = document.querySelector("button");
// 绑定 this 为 user,返回新函数作为事件处理函数(点击时才执行)
btn.addEventListener("click", user.sayHello.bind(user));
// 若直接绑定 user.sayHello,点击时 this 指向 btn,会输出 undefined 点击了按钮
// btn.addEventListener("click", user.sayHello); // 错误用法
场景2:定时器/延时器中固定 this 指向
定时器 setTimeout/setInterval 的回调函数中,this 默认指向全局对象(浏览器中是 window),通过 bind 可固定其 this 指向自定义上下文。
javascript
const person = {
name: "小李",
delayedSay: function() {
// 延迟 1 秒执行,通过 bind 绑定 this 为 person
setTimeout(function() {
console.log(`我是${this.name},延迟 1 秒执行`);
}.bind(this), 1000);
}
};
person.delayedSay(); // 1 秒后输出:我是小李,延迟 1 秒执行
// 若不使用 bind,this 指向 window,会输出:我是undefined,延迟 1 秒执行
场景3:函数柯里化(预先传递部分参数,延迟传递剩余参数)
bind 支持预先传递部分参数,返回的新函数只需接收剩余参数即可执行,这是实现函数柯里化的常用方式之一。
javascript
// 原始函数:计算三个数的和
function add(a, b, c) {
return a + b + c;
}
// 使用 bind 预先传递第一个参数 10,返回新函数
const add10 = add.bind(null, 10);
// 后续调用新函数,只需传递剩余两个参数
console.log(add10(20, 30)); // 60(10+20+30)
console.log(add10(5, 15)); // 30(10+5+15)
// 进一步柯里化:预先传递前两个参数
const add10And20 = add.bind(null, 10, 20);
console.log(add10And20(50)); // 80(10+20+50)
五、总结
- call:参数明确需逐个传递、需立即执行,核心场景是「方法借用」(类数组操作)、「构造函数属性继承」。
- apply:参数已在数组中、需立即执行,核心场景是「数组求最大/最小值」、「批量传递数组参数」。
- bind:需延迟执行(事件/定时器)、需固定 this 指向,核心场景是「事件处理」、「定时器上下文绑定」、「函数柯里化」。
- 三者核心都是改变
this指向,区别仅在于「执行时机」和「参数传递方式」,可根据实际场景灵活选择。