一、基础定义与共性
三者均为 Function.prototype 的方法,用于显式绑定函数的 this 上下文,并支持参数传递。
二、核心区别对比表
| 方法 | 立即执行? | 参数传递方式 | 返回值 | 常见应用场景 |
|---|---|---|---|---|
call | ✅ 立即执行 | thisArg, arg1, arg2, ...(参数列表) | 函数执行结果 | 继承、单次调用并指定 this |
apply | ✅ 立即执行 | thisArg, [argsArray](参数数组) | 函数执行结果 | 参数不确定时(如 Math.max 应用于数组) |
bind | ❌ 不执行 | thisArg, arg1, arg2, ...(预设参数) | 新函数(需手动调用) | 创建固定 this 的回调函数、偏函数 |
三、代码示例与场景
1. call:逐个传递参数
const person = { name: 'Alice' };
function greet(message) {
console.log(`${message}, ${this.name}`);
}
greet.call(person, 'Hello'); // "Hello, Alice"
2. apply:参数数组
const numbers = [5, 3, 8, 1];
const max = Math.max.apply(null, numbers); // 等价于 Math.max(5, 3, 8, 1)
console.log(max); // 8
3. bind:创建绑定函数
const counter = {
count: 0,
increment() {
this.count++;
console.log(this.count);
}
};
const boundIncrement = counter.increment.bind(counter);
setTimeout(boundIncrement, 1000); // 1(避免 this 指向 window)
四、问题
1. 问:如何用 call 实现继承?
- 在子类构造函数中通过 `call` 调用父类构造函数,绑定子类的 `this`:
```javascript
function Parent(name) {
this.name = name;
}
function Child(name, age) {
Parent.call(this, name); // 继承 name 属性
this.age = age;
}
```
2. 问:bind 为什么适合作为回调函数?
- 回调函数常被异步调用(如 `setTimeout`、事件监听),此时 `this` 默认指向全局对象(如 `window`)。
- `bind` 可预先绑定 `this`,确保回调执行时上下文正确:
```javascript
button.addEventListener('click', this.handleClick.bind(this));
```
3. 问:如何用 apply 实现数组展开?
- 利用 `apply` 将数组作为参数传递给函数:
```javascript
const arr1 = [1, 2];
const arr2 = [3, 4];
arr1.push.apply(arr1, arr2); // 等价于 arr1.push(3, 4)
console.log(arr1); // [1, 2, 3, 4]
```
4. 问:bind 能否被多次调用?
- 可以,但后续 `bind` 会**叠加参数**,而非覆盖:
```javascript
function add(a, b) { return a + b; }
const add5 = add.bind(null, 5); // 预设 a=5
const add10 = add5.bind(null, 10); // 预设 a=10(但 a 已被 bind 为 5)
console.log(add5(3)); // 8(5+3)
console.log(add10(3)); // 15(5+10,第二个参数被忽略)
```
五、性能与最佳实践
1. 性能对比
call/apply:直接执行函数,开销较小。bind:创建新函数实例,内存占用更高(尤其频繁调用时)。
2. 替代方案
- 箭头函数:继承外层
this,可替代部分bind场景:setTimeout(() => this.handleClick(), 1000); // 无需 bind
3. 实现简易版 bind
Function.prototype.myBind = function(context) {
const self = this;
const args = Array.prototype.slice.call(arguments, 1);
return function() {
return self.apply(context, args.concat(Array.prototype.slice.call(arguments)));
};
};