在 JavaScript 中, 的指向通常由调用方式决定。面试中要求手写这三个方法,核心考察的是对 “隐式绑定” 原理的理解。
this
核心原理:
当函数作为某个对象的方法被调用时(例如 ),
obj.func() 自然指向该对象。
this
因此,我们要做的就是:把函数临时挂载到目标对象上,执行它,然后再删掉它。
1. 手写
call
call
方法接收两个参数:
call
:this 要指向的对象。
context:参数列表(逗号分隔)。
...args
实现步骤
判断 是否存在,如果为
context 或
null,默认指向
undefined (或
window)。为了避免属性名冲突,覆盖了 context 原有的属性,使用
globalThis 创建一个唯一的 key。将当前函数(
Symbol)赋值给
this。执行这个函数,并传入参数。删除临时添加的属性(清理现场)。返回函数执行的结果。
context[key]
源码实现
Function.prototype.myCall = function(context, ...args) {
// 1. 边界判断与默认值
if (context === null || context === undefined) {
context = window; // 浏览器环境
}
// 也可以写成: context = context || window;
// 2. 将当前函数 (this) 挂载到 context 上
// 使用 Symbol 避免覆盖 context 上已有的同名属性
const fnSymbol = Symbol('fn');
context[fnSymbol] = this;
// 3. 执行函数,并展开参数
const result = context[fnSymbol](...args);
// 4. 删除临时属性,恢复原样
delete context[fnSymbol];
// 5. 返回结果
return result;
};
测试
const person = { name: 'Alice' };
function say(age, job) {
console.log(`${this.name} is ${age} years old, working as ${job}`);
return 'done';
}
say.myCall(person, 25, 'Developer');
// 输出: "Alice is 25 years old, working as Developer"
2. 手写
apply
apply
与
apply 的唯一区别在于传参方式:
call 接收一个数组作为参数。
apply
源码实现
Function.prototype.myApply = function(context, argsArray) {
if (context === null || context === undefined) {
context = window;
}
const fnSymbol = Symbol('fn');
context[fnSymbol] = this;
let result;
// 判断是否有参数数组
if (argsArray && Array.isArray(argsArray)) {
result = context[fnSymbol](...argsArray);
} else {
result = context[fnSymbol]();
}
delete context[fnSymbol];
return result;
};
3. 手写
bind (难点 🔥)
bind
不会立即执行函数,而是返回一个新的函数。
bind
它有两个复杂的特性需要实现:
柯里化 (Currying):参数可以分两次传(绑定时传一部分,执行时传另一部分)。构造函数效果 (New Binding):如果返回的函数被 调用,
new 应该指向新创建的实例,而不是原本绑定的
this。
context
源码实现
Function.prototype.myBind = function(context, ...args1) {
// 0. 错误判断:调用 myBind 的必须是函数
if (typeof this !== 'function') {
throw new TypeError('Error: what is trying to be bound is not callable');
}
// 保存原函数
const self = this;
// 1. 返回一个新的函数
// args2 是将来执行时传入的参数
const fBound = function(...args2) {
// 2. 处理 `new` 调用的情况 (关键点)
// 如果当前函数被 new 调用,this 会是 fBound 的实例
// 此时我们要忽略传入的 context,将 this 指向这个实例
const isNewCall = this instanceof fBound;
return self.apply(
isNewCall ? this : context,
[...args1, ...args2] // 合并参数
);
};
// 3. 维护原型链
// 确保 new 出来的实例能继承原函数原型链上的属性
// 使用 Object.create 创建一个空对象作为中介,避免直接修改 prototype 导致副作用
if (self.prototype) {
fBound.prototype = Object.create(self.prototype);
}
return fBound;
};
核心难点解析:为什么需要
this instanceof fBound?
this instanceof fBound
当你使用 操作符时,JS 内部会创建一个新对象,并将函数内部的
new 指向这个新对象。
this
但在 返回的函数中,我们手动指定了
bind。
apply(context)
如果不加判断, 时
new BoundFn() 依然会被强行改为
this,这违背了
context 的语义(
new 的优先级高于
new)。
bind
所以需要判断:如果我是被 调用的,就不要管那个
new 了,直接用当前的
context。
this
测试验证
// 1. 普通绑定测试
const obj = { val: 100 };
function add(a, b) {
console.log(this.val + a + b);
}
const boundAdd = add.myBind(obj, 10); // 预传参数 10
boundAdd(20); // 输出 130 (100 + 10 + 20)
// 2. new 调用测试 (高级)
function Person(name) {
this.name = name;
}
const BoundPerson = Person.myBind({ name: 'BadContext' }); // 试图绑定到一个错误对象
const p = new BoundPerson('GoodName');
console.log(p.name); // 输出 'GoodName'。如果实现错误,这里可能会输出 'BadContext'
总结
| 方法 | 是否立即执行 | 传参形式 | 主要用途 |
|---|---|---|---|
| call | ✅ 是 | |
改变一次执行上下文,借用父类构造函数 |
| apply | ✅ 是 | |
改变上下文,且参数本来就是数组(如求数组最大值 ) |
| bind | ❌ 否 | |
保存上下文,后续执行(如 React 事件处理、setTimeout 回调) |


