在前端面试中,「手动实现 bind 方法」是高频考察点 —— 它不仅能验证你对 this 绑定、函数原型、构造函数等核心知识点的理解,还能体现代码的健壮性思维。本文会从 bind 的原生特性出发,拆解手写 bind 的核心逻辑,结合完整代码逐行解析,让你彻底搞懂背后的原理。
一、先搞懂:原生 bind 到底做了什么?
在手写之前,先明确原生 Function.prototype.bind 的核心特性,这是我们手写的「对标标准」:
- 返回新函数:bind 不会立即执行原函数,而是返回一个绑定了
this的新函数; - 绑定 this 指向:指定新函数执行时的
this上下文,且绑定后无法被 call/apply 修改; - 支持预置参数(柯里化):调用 bind 时可传入部分参数,新函数执行时可传入剩余参数;
- 兼容构造函数场景:如果绑定后的函数被当作构造函数(new 调用),this 会指向实例,而非 bind 传入的对象;
- 保留原型链:绑定后的新函数能继承原函数的原型属性。
举个原生 bind 的例子:
const person = { name: '张三' };
function sayHi(age, gender) {
console.log(`我是${this.name},${age}岁,${gender}`);
}
// 绑定this+预置参数age=18
const bindSayHi = sayHi.bind(person, 18);
// 执行时传入剩余参数gender
bindSayHi('男'); // 输出:我是张三,18岁,男
二、手写 bind 完整代码(带详细注释)
基于原生特性,我们实现一个「功能对齐、鲁棒性强」的 myBind 方法,先看完整代码:
Function.prototype.myBind = function (obj, ...args) {
// 1. 保存原函数(this 指向调用 myBind 的原函数,比如上面的 sayHi)
const fn = this;
// 2. 处理绑定对象:null/undefined 时绑定到 window,否则转为对象(兼容基本类型)
obj = (obj !== null && obj !== undefined) ? Object(obj) : window;
// 3. 定义返回的新函数 F,支持接收剩余参数
const F = function (...rest) {
// 4. 关键:区分构造函数场景(new 调用)和普通调用
if (this instanceof F) {
// new 调用时:this 指向 F 的实例,原函数作为构造函数执行
return new fn(...args, ...rest);
}
// 普通调用时:用 call 绑定 this 到 obj,合并预置参数和新参数
return fn.call(obj, ...args, ...rest);
};
// 5. 修复原型链:让新函数 F 继承原函数的原型
F.prototype = Object.create(fn.prototype);
// 修正 constructor 指向,避免原型链混乱
F.prototype.constructor = F;
// 6. 返回绑定后的新函数
return F;
};
三、逐行拆解:核心知识点与实现逻辑
1. 保存原函数:const fn = this
- 知识点:函数的
this指向 —— 当调用fn.myBind(obj)时,this指向调用myBind的原函数fn(比如上面的sayHi)。 - 作用:后续执行新函数时,需要通过这个变量调用原函数,避免
this丢失。
2. 处理绑定对象:obj = Object(obj) || window
- 知识点:
this的绑定规则 + 基本类型包装对象。 - 细节:
- 如果传入的
obj是null/undefined,按原生 bind 规则,this绑定到全局对象(浏览器中是window); - 如果传入的是基本类型(比如
123、'abc'),用Object()转为包装对象(比如Number {123}),避免this绑定到基本类型(基本类型无法作为this指向)。
- 如果传入的
3. 定义新函数 F:接收剩余参数
- 知识点:剩余参数
...rest+ 函数柯里化。 - 作用:
myBind调用时传入的...args是「预置参数」,新函数 F 执行时传入的...rest是「剩余参数」,最终合并传给原函数(比如fn(...args, ...rest)),实现柯里化。
4. 区分构造函数 / 普通调用
这是手写 bind 最核心的难点,也是面试高频考点:
- 知识点:
new操作符的原理 +instanceof检测实例关系。 - 逻辑解析:
- 当用
new调用绑定后的函数(比如new bindSayHi()),this应该指向新创建的实例,而非myBind传入的obj; this instanceof F:判断当前this是否是 F 的实例(即是否通过new调用);- 如果是构造函数场景:用
new fn(...args, ...rest)执行原函数,此时原函数的this指向实例; - 如果是普通场景:用
fn.call(obj, ...args, ...rest)执行原函数,强制绑定this到obj。
- 当用
5. 修复原型链:避免断裂
- 知识点:原型链继承 +
Object.create原理。 - 问题:如果直接
F.prototype = fn.prototype,修改 F 的原型会污染原函数的原型; - 解决方案:
F.prototype = Object.create(fn.prototype):创建一个以fn.prototype为原型的空对象,作为 F 的原型,实现「继承原型但不污染原原型」;F.prototype.constructor = F:Object.create会导致 F.prototype 的 constructor 指向fn,手动修正为 F,保证原型链的正确性。
四、测试验证:手写 myBind 是否符合预期
我们用原生 bind 的测试场景验证 myBind:
const person = { name: '张三' };
function sayHi(age, gender) {
console.log(`我是${this.name},${age}岁,${gender}`);
// 构造函数场景:验证 this 指向实例
this.age = age;
}
// 1. 普通调用场景
const myBindSayHi = sayHi.myBind(person, 18);
myBindSayHi('男'); // 输出:我是张三,18岁,男
// 2. 构造函数场景
const instance = new myBindSayHi('女');
console.log(instance.age); // 输出:18(this 指向实例,而非 person)
console.log(instance instanceof sayHi); // 输出:true(继承原函数原型)
五、核心知识点总结
手写 bind 的过程,本质是对以下 JS 核心知识点的综合运用:
- this 绑定规则:区分普通调用、构造函数调用的 this 指向;
- 函数的原型与原型链:通过
Object.create实现原型继承,修正 constructor 指向; - 剩余参数与柯里化:合并 bind 预置参数和新函数执行参数;
- new 操作符原理:
new调用时 this 指向实例,且会继承原构造函数的原型; - 基本类型包装对象:处理绑定对象为基本类型的边界场景。
六、面试加分点
在面试中手写 bind 时,除了写出核心逻辑,还可以补充这些「鲁棒性细节」,体现你的工程思维:
- 校验调用 myBind 的是否是函数:
if (typeof this !== 'function') throw new TypeError('not a function'); - 兼容严格模式(严格模式下
null/undefined不会转为 window); - 解释
Object.create替代方案(比如自定义空函数中转)。
最后
手动实现 bind 不是「背代码」,而是理解每一行背后的 JS 设计思想 —— 比如 this 的动态指向、原型链的继承逻辑、构造函数的特性。掌握这些底层逻辑,不仅能搞定面试,更能在日常开发中快速定位「this 丢失」「原型链混乱」等问题。
&spm=1001.2101.3001.5002&articleId=159395784&d=1&t=3&u=b5e014b13fc7412c91e90e98e4fa5a61)
1271

被折叠的 条评论
为什么被折叠?



