实现 call、apply、bind
手写 JavaScript 的 call、apply 和 bind 方法
问题
从零实现 call、apply 和 bind 方法。
解答
实现 call
Function.prototype.myCall = function(context, ...args) {
// 如果 context 为 null 或 undefined,指向全局对象
context = context ?? globalThis;
// 将原始值转换为对象
context = Object(context);
// 用 Symbol 避免属性名冲突
const fn = Symbol('fn');
// 将函数作为 context 的方法
context[fn] = this;
// 调用函数并获取结果
const result = context[fn](...args);
// 删除临时属性
delete context[fn];
return result;
};
实现 apply
Function.prototype.myApply = function(context, args = []) {
context = context ?? globalThis;
context = Object(context);
const fn = Symbol('fn');
context[fn] = this;
// 与 call 的区别:参数是数组
const result = context[fn](...args);
delete context[fn];
return result;
};
实现 bind
Function.prototype.myBind = function(context, ...args) {
const self = this;
const boundFn = function(...newArgs) {
// 判断是否作为构造函数调用
// 如果是 new 调用,this 指向实例,忽略绑定的 context
return self.apply(
this instanceof boundFn ? this : context,
[...args, ...newArgs]
);
};
// 维护原型链,让 new 出来的实例能访问原函数原型上的属性
if (self.prototype) {
boundFn.prototype = Object.create(self.prototype);
}
return boundFn;
};
测试代码
const obj = { name: 'Alice' };
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
return this.name;
}
// 测试 call
greet.myCall(obj, 'Hello', '!'); // Hello, Alice!
// 测试 apply
greet.myApply(obj, ['Hi', '?']); // Hi, Alice?
// 测试 bind
const boundGreet = greet.myBind(obj, 'Hey');
boundGreet('~'); // Hey, Alice~
// 测试 bind + new
function Person(name) {
this.name = name;
}
const BoundPerson = Person.myBind({ name: 'ignored' });
const p = new BoundPerson('Bob');
console.log(p.name); // Bob(new 时忽略绑定的 context)
关键点
- 使用
Symbol作为临时属性名,避免覆盖 context 上的已有属性 context ?? globalThis处理 null/undefined,Object(context)处理原始值call和apply的区别仅在于参数传递方式:逐个 vs 数组bind返回新函数,需要合并预设参数和调用时参数bind要处理new调用的情况:通过instanceof判断,是则忽略绑定的 context
目录