算法 - 手写 call / apply / bind
1. 题目
在 Function.prototype 上实现三个方法(挂到自定义名字以免污染):
myCall(thisArg, ...args):以thisArg为this、逐个传参,立即调用函数。myApply(thisArg, argsArray):同上,但参数以数组形式传入。myBind(thisArg, ...partialArgs):返回一个新函数,绑定this并可柯里化预置部分参数;新函数被new时,this绑定失效(指向新建实例),但预置参数仍生效。
要求处理 thisArg 为原始值(应装箱/或在全局环境忽略)、null/undefined(指向全局)等边界。
2. 解题思路
三者核心都是一句话:让函数以某个对象作为 this 来执行。技巧是把函数临时设为该对象的属性,通过 obj.fn() 调用,此时 this 自然指向 obj,调用完删除临时属性。
2.1 call / apply
thisArg为null/undefined时用全局对象(浏览器globalThis)。- 把原始值
Object(thisArg)装箱成对象(严格模式下 call 传入原始值 this 仍是原始值,这里按非严格近似)。 - 用唯一的
Symbol作为临时属性名,避免与已有 key 冲突。 - 调用后
delete掉临时属性。 apply只是把数组参数展开传入。
2.2 bind
bind 不立即执行,而是返回一个 boundFunction:
普通调用时,
this固定为绑定对象,且把预置参数和调用时参数拼接(柯里化)。用
new调用 boundFunction 时,ES 规范要求忽略绑定的this,创建全新实例,但预置参数仍前置。实现关键:new bind(Fn)时原型链上boundFunction.prototype === Fn.prototype,因此判断this instanceof boundFunction来决定用thisArg还是新建对象。时间复杂度:调用时才确定,均
O(参数)。
3. TypeScript 实现
1 | // myCall |
4. 面试延伸
- bind 的
new语义是本题最大的区分点:很多人 bind 只会apply,答不出new.target判断与boundFunction.prototype的桥接,原型链一断instanceof就错。 - Symbol 临时属性名:用
Symbol()或fn_${Date.now()}避免覆盖对象上同名方法,比直接写ctx.fn严谨。 - 严格模式差异:严格模式下
call(1)的this就是1而非装箱对象,Object(thisArg)是近似;能点出这条即显深度。 - 三者关系:
call/apply只传参方式不同、都立即执行;bind返回新函数且能柯里化,底层常复用apply。 - 关联:
instanceof手写见下一篇 [手写 instanceof],同属「理解原型与 this」的必考组合。
难度:中等 | 手写原型三件套 | this 绑定机制