JavaScript中bind、apply、call的区别及实现
bind、apply、call 的区别及内在实现
核心区别
| 方法 | 调用方式 | 参数传递 | 立即执行 | 返回值 |
|---|---|---|---|---|
call |
直接调用 | 参数列表 | 是 | 函数执行结果 |
apply |
直接调用 | 参数数组 | 是 | 函数执行结果 |
bind |
返回新函数 | 参数列表 | 否 | 绑定this的新函数 |
详细解析
1. call 方法
function greet(message, punctuation) {
return `${message}, ${this.name}${punctuation}`;
}
const person = { name: 'Alice' };
// 使用 call
const result = greet.call(person, 'Hello', '!');
console.log(result); // "Hello, Alice!"
2. apply 方法
function greet(message, punctuation) {
return `${message}, ${this.name}${punctuation}`;
}
const person = { name: 'Bob' };
// 使用 apply
const result = greet.apply(person, ['Hi', '!!']);
console.log(result); // "Hi, Bob!!"
3. bind 方法
function greet(message, punctuation) {
return `${message}, ${this.name}${punctuation}`;
}
const person = { name: 'Charlie' };
// 使用 bind
const boundGreet = greet.bind(person, 'Welcome');
const result = boundGreet('!!!');
console.log(result); // "Welcome, Charlie!!!"
内在实现原理
1. call 的模拟实现
Function.prototype.myCall = function(context, ...args) {
// 如果 context 为 null 或 undefined,则指向全局对象
context = context || window;
// 创建一个唯一的属性名,避免覆盖原有属性
const fnKey = Symbol('fn');
// 将当前函数作为 context 的方法
context[fnKey] = this;
// 执行函数
const result = context[fnKey](...args);
// 删除临时添加的方法
delete context[fnKey];
return result;
};
// 测试
function test(a, b) {
console.log(this.value, a, b);
}
const obj = { value: 'test' };
test.myCall(obj, 1, 2); // 输出: test 1 2
2. apply 的模拟实现
Function.prototype.myApply = function(context, argsArray = []) {
// 如果 context 为 null 或 undefined,则指向全局对象
context = context || window;
// 创建一个唯一的属性名
const fnKey = Symbol('fn');
// 将当前函数作为 context 的方法
context[fnKey] = this;
// 执行函数,传递数组参数
const result = context[fnKey](...argsArray);
// 删除临时添加的方法
delete context[fnKey];
return result;
};
// 测试
function sum(a, b, c) {
return this.base + a + b + c;
}
const obj = { base: 10 };
const result = sum.myApply(obj, [1, 2, 3]);
console.log(result); // 输出: 16
3. bind 的模拟实现
Function.prototype.myBind = function(context, ...bindArgs) {
const originalFunc = this;
return function(...callArgs) {
// 合并绑定时的参数和调用时的参数
const allArgs = bindArgs.concat(callArgs);
// 判断是否通过 new 调用
if (new.target) {
// 如果是 new 调用,忽略绑定的 this
return new originalFunc(...allArgs);
} else {
// 普通调用,使用绑定的 this
return originalFunc.apply(context, allArgs);
}
};
};
// 测试
function Person(name, age) {
this.name = name;
this.age = age;
}
const BoundPerson = Person.myBind(null, 'John');
const person = new BoundPerson(25);
console.log(person); // Person { name: 'John', age: 25 }
实际应用场景
1. 借用数组方法处理类数组对象
// 类数组对象
const arrayLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
// 使用 apply 借用数组方法
const realArray = Array.prototype.slice.call(arrayLike);
console.log(realArray); // ['a', 'b', 'c']
2. 函数柯里化
function multiply(a, b, c) {
return a * b * c;
}
// 使用 bind 实现柯里化
const multiplyByTwo = multiply.bind(null, 2);
const multiplyByTwoAndThree = multiplyByTwo.bind(null, 3);
console.log(multiplyByTwoAndThree(4)); // 24 (2 * 3 * 4)
3. 事件处理函数绑定 this
class Button {
constructor() {
this.text = 'Click me';
// 使用 bind 确保方法中的 this 指向实例
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(`Button text: ${this.text}`);
}
attachEvent() {
document.getElementById('myButton').addEventListener('click', this.handleClick);
}
}
注意事项
- 严格模式下的区别:在严格模式下,如果 call/apply 的第一个参数是原始值,不会被转换为对象
- 性能考虑:bind 会创建新函数,在性能敏感的场景要谨慎使用
- 箭头函数:箭头函数没有自己的 this,call/apply/bind 无法改变其 this 指向
// 箭头函数的 this 无法被改变
const arrowFunc = () => console.log(this);
const obj = { value: 'test' };
arrowFunc.call(obj); // 仍然指向定义时的 this,不是 obj
这三种方法都是 JavaScript 中非常重要的函数方法,理解它们的区别和实现原理对于编写高质量的 JavaScript 代码至关重要。
挣钱养家

浙公网安备 33010602011771号