1. 实现 call 方法
call()
方法使用一个指定的 this
值和单独给出的一个或多个参数来调用一个函数。
实现思路:
- 将函数设置为对象的属性
- 执行该函数
- 删除该属性(避免污染对象)
实现代码:
javascript
Function.prototype.myCall = function(context, ...args) {
// 如果context为null或undefined,则指向全局对象(浏览器中是window)
context = context || window;
// 创建一个唯一的属性名,避免覆盖原有属性
const fnSymbol = Symbol('fn');
// 将当前函数(this)赋值给context的fnSymbol属性
context[fnSymbol] = this;
// 执行函数
const result = context[fnSymbol](...args);
// 删除临时属性
delete context[fnSymbol];
return result;
};
// 测试用例
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
const person = { name: 'John' };
greet.myCall(person, 'Hello', '!'); // 输出: "Hello, John!"
2. 实现 apply 方法
apply()
方法与 call()
类似,只是参数以数组(或类数组对象)的形式传递。
实现代码:
javascript
Function.prototype.myApply = function(context, argsArray) {
// 如果context为null或undefined,则指向全局对象
context = context || window;
// 创建一个唯一的属性名
const fnSymbol = Symbol('fn');
// 将当前函数赋值给context的fnSymbol属性
context[fnSymbol] = this;
// 执行函数,处理argsArray为null或undefined的情况
const result = argsArray ? context[fnSymbol](...argsArray) : context[fnSymbol]();
// 删除临时属性
delete context[fnSymbol];
return result;
};
// 测试用例
function introduce(language, years) {
console.log(`I'm ${this.name}, I code in ${language} for ${years} years.`);
}
const dev = { name: 'Alice' };
introduce.myApply(dev, ['JavaScript', 5]); // 输出: "I'm Alice, I code in JavaScript for 5 years."
3. 实现 bind 方法
bind()
方法创建一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,其余参数将作为新函数的参数供调用时使用。
实现思路:
- 返回一个新函数
- 新函数执行时,使用指定的
this
值 - 支持柯里化(参数分次传递)
实现代码:
javascript
Function.prototype.myBind = function(context, ...bindArgs) {
const originalFunc = this;
return function(...callArgs) {
// 判断是否作为构造函数调用(使用new操作符)
if (new.target) {
return new originalFunc(...bindArgs, ...callArgs);
}
// 普通函数调用
const fnSymbol = Symbol('fn');
context = context || window;
context[fnSymbol] = originalFunc;
const result = context[fnSymbol](...bindArgs, ...callArgs);
delete context[fnSymbol];
return result;
};
};
// 测试用例
function printInfo(role, department) {
console.log(`${this.name} is ${role} in ${department}`);
}
const employee = { name: 'Bob' };
const boundFunc = printInfo.myBind(employee, 'developer');
boundFunc('IT'); // 输出: "Bob is developer in IT"
// 测试作为构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
const BoundPerson = Person.myBind(null, 'DefaultName');
const p = new BoundPerson(30);
console.log(p); // 输出: Person { name: 'DefaultName', age: 30 }
4. 完整实现与边界情况处理
完整实现(处理更多边界情况):
javascript
Function.prototype.myFullBind = function(context, ...bindArgs) {
if (typeof this !== 'function') {
throw new TypeError('Bind must be called on a function');
}
const originalFunc = this;
const boundFunc = function(...callArgs) {
// 判断是否作为构造函数调用
const isConstructorCall = new.target;
return originalFunc.apply(
isConstructorCall ? this : (context || globalThis),
bindArgs.concat(callArgs)
);
};
// 维护原型关系
if (originalFunc.prototype) {
boundFunc.prototype = Object.create(originalFunc.prototype);
}
return boundFunc;
};
边界情况说明:
- 构造函数调用 :当使用
new
操作符调用绑定函数时,this
应该指向新创建的对象,而不是绑定的context
- 原型链维护:确保绑定后的函数能正确继承原函数的原型链
- 严格模式 :在严格模式下,
context
为null
或undefined
时,this
不会自动转为全局对象 - 参数合并:正确处理绑定时参数和调用时参数的合并
5. 三种方法的对比总结
方法 | 调用方式 | 参数形式 | 立即执行 | 返回新函数 |
---|---|---|---|---|
call | func.call(ctx, arg1, arg2) |
参数列表 | 是 | 否 |
apply | func.apply(ctx, [arg1, arg2]) |
数组形式参数 | 是 | 否 |
bind | func.bind(ctx, arg1, arg2) |
参数列表 | 否 | 是 |
6. 实际应用场景
call/apply 使用场景:
- 借用方法:
javascript
// 类数组对象使用数组方法
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
Array.prototype.push.myCall(arrayLike, 'c');
console.log(arrayLike); // { 0: 'a', 1: 'b', 2: 'c', length: 3 }
- 确定函数上下文:
javascript
function logThis() {
console.log(this);
}
logThis.myCall({ id: 42 }); // 输出: { id: 42 }
bind 使用场景:
- 事件处理函数:
javascript
class Button {
constructor() {
this.text = 'Click me';
this.handleClick = this.handleClick.myBind(this);
}
handleClick() {
console.log(this.text);
}
}
const btn = new Button();
document.querySelector('button').addEventListener('click', btn.handleClick);
- 参数预设(柯里化):
javascript
function multiply(a, b) {
return a * b;
}
const double = multiply.myBind(null, 2);
console.log(double(5)); // 10