1. 手写 call 方法
核心思路:改变函数的 this 指向并立即执行,通过将函数临时挂载到目标对象上调用。
Function.prototype.myCall = function (context, ...args) {
// 如果 context 为 null 或 undefined,则默认为 window
context = context || window;
// 将当前函数作为 context 的一个属性
const fnKey = Symbol(); // 使用 Symbol 避免属性名冲突
context[fnKey] = this;
// 调用函数
const result = context[fnKey](...args);
// 删除临时添加的属性
delete context[fnKey];
return result;
};
// 使用示例
function greet(name, age) {
console.log(`Hello, ${name}! You are ${age} years old.`);
console.log(`This is ${this.title}`);
}
const context = { title: 'My Context' };
greet.myCall(context, 'Alice', 25);
将函数作为上下文对象的一个属性调用,调用后删除该属性,接收参数列表。
2. 手写 apply 方法
核心思路:与 call 类似,但参数以数组形式传递。
Function.prototype.myApply = function (context, argsArray) {
// 如果 context 为 null 或 undefined,则默认为 window
context = context || window;
// 将当前函数作为 context 的一个属性
const fnKey = Symbol();
context[fnKey] = this;
// 调用函数,apply 接收数组参数
const result = context[fnKey](...argsArray);
// 删除临时添加的属性
delete context[fnKey];
return result;
};
// 使用示例
function greet(name, age) {
console.log(`Hello, ${name}! You are ${age} years old.`);
console.log(`This is ${this.title}`);
}
const context = { title: 'My Context' };
greet.myApply(context, ['Bob', 30]);
将函数作为上下文对象的一个属性调用,调用后删除该属性,接收参数数组。
3. 手写 bind 方法
核心思路:返回一个新函数,该函数固定 this 指向和部分参数,调用时合并剩余参数。
Function.prototype.myBind = function (context, ...bindArgs) {
const originalFunc = this;
return function (...callArgs) {
// 合并绑定时和调用时的参数
const allArgs = bindArgs.concat(callArgs);
// 使用 apply 方法调用原始函数
return originalFunc.apply(context, allArgs);
};
};
// 使用示例
function greet(name, age) {
console.log(`Hello, ${name}! You are ${age} years old.`);
console.log(`This is ${this.title}`);
}
const context = { title: 'My Context' };
const boundGreet = greet.myBind(context, 'Charlie');
boundGreet(35);
// Hello, Charlie! You are 35 years old. This is My Context
返回一个新函数,合并绑定时的参数和调用时的参数,使用 apply 调用原始函数。
边界情况:构造函数调用:若返回的函数被用作构造函数(通过 new 调用),原生 bind 绑定的 this 会失效,指向新实例。但当前实现未处理此情况,需额外判断(见下方优化代码)。
Function.prototype.myBind = function (context, ...bindArgs) {
const originalFunc = this;
const boundFunc = function (...callArgs) {
// 判断是否通过 new 调用
const isNewCall = this instanceof boundFunc;
const thisArg = isNewCall ? this : context;
return originalFunc.apply(thisArg, bindArgs.concat(callArgs));
};
// 维护原型关系(确保 new 操作符正确工作)
boundFunc.prototype = Object.create(originalFunc.prototype);
return boundFunc;
};
4. 手写 new 操作符
核心思路:模拟构造函数实例化过程:创建对象、绑定原型、初始化属性、处理返回值。
function myNew(constructor, ...args) {
// 创建一个新对象,并将其原型指向构造函数的 prototype
const obj = Object.create(constructor.prototype);
// 调用构造函数,将 this 绑定到新创建的对象
const result = constructor.apply(obj, args);
// 如果构造函数返回了一个对象,则返回该对象
// 否则返回新创建的对象
return result instanceof Object ? result : obj;
}
// 使用示例
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function () {
console.log(`Hi, I'm ${this.name}, ${this.age} years old.`);
};
const john = myNew(Person, 'John', 28);
john.greet();
// Hi, I'm John, 28 years old.
创建一个新对象,并将其原型指向构造函数的 prototype,调用构造函数,绑定 this 到新对象,处理构造函数的返回值(如果返回对象则使用该对象)
5. 总结对比
通过手写实现这些核心方法,可以更深入理解 JavaScript 的函数执行机制、this 绑定规则和原型继承原理。实际开发中,建议直接使用原生方法(性能更优),但掌握原理对调试和进阶学习至关重要!