✅《JS 手写系列》第 1 篇:彻底搞懂 call
、apply
和 bind
🎯 一、为什么要手写这几个函数?
- 它们是 JavaScript 中 Function 原型链上非常核心的函数。
- 实际开发中经常用于改变函数的执行上下文(this)。
- 大厂面试高频必考(小公司不考别怕),考你理解 this、本质执行流程、原型继承。
🔍 二、面试官的考察重点
-
你是否知道 call/apply/bind 本质上是让函数以指定 this 执行?
-
你是否清楚区别:
call
:立即执行 + 传参列表apply
:立即执行 + 传参数组bind
:返回一个新函数,不立即执行
🧠 三、原理拆解与实现步骤
3.1 核心思路
js
fn.call(obj, arg1, arg2)
// 等价于:obj.fn = fn; obj.fn(arg1, arg2); delete obj.fn
✍️ 四、手写 call
js
Function.prototype.myCall = function (context, ...args) {
context = context || globalThis; // 处理 null/undefined
const fnSymbol = Symbol('fn'); // 防止属性覆盖
context[fnSymbol] = this; // this 是当前函数
const result = context[fnSymbol](...args); // 执行函数
delete context[fnSymbol]; // 清理临时属性
return result; // 返回结果
};
✅ 用例验证
js
function greet(greeting, name) {
console.log(`${greeting}, ${name} from ${this.city}`);
}
const context = { city: 'Guangzhou' };
greet.myCall(context, 'Hi', 'Mark'); // Hi, Mark from Guangzhou
✍️ 五、手写 apply
js
Function.prototype.myApply = function (context, args) {
context = context || globalThis;
const fnSymbol = Symbol('fn');
context[fnSymbol] = this;
const result = Array.isArray(args)
? context[fnSymbol](...args)
: context[fnSymbol]();
delete context[fnSymbol];
return result;
};
✅ 用例验证
js
greet.myApply(context, ['Hello', 'Hao']); // Hello, Hao from Guangzhou
✍️ 六、手写 bind
js
Function.prototype.myBind = function (context, ...args) {
const self = this;
return function (...restArgs) {
return self.apply(context, [...args, ...restArgs]);
};
};
✅ 用例验证
js
const greetBound = greet.myBind(context, 'Yo');
greetBound('Fu'); // Yo, Fu from Guangzhou
🔁 七、进阶版本:支持 new 调用的 bind
js
Function.prototype.myBind = function (context, ...args) {
const self = this;
function boundFunction(...restArgs) {
// 如果是用 new 调用
if (this instanceof boundFunction) {
return new self(...args, ...restArgs);
}
return self.apply(context, [...args, ...restArgs]);
}
// 原型继承
boundFunction.prototype = Object.create(self.prototype);
return boundFunction;
};
❗ 八、易错点总结(面试特别爱问)
错误理解 | 正确做法 |
---|---|
bind 会立即执行 |
❌,它返回一个函数,不会立即执行 |
没有处理 new 场景 |
✅ 用 this instanceof 区分 |
覆盖对象属性名 | ✅ 用 Symbol 解决 |