bind
以下是 JavaScript 中 bind
方法的实现原理及关键步骤解析:
//bind函数
Function.prototype.myBind = function (context = window, ...initialArgs) {
// 检查调用 myBind 的是否为函数
if (typeof this!== 'function') {
throw new TypeError('调用 myBind 的对象必须是函数');
}
// 保存调用 myBind 的原始函数
const originalFunction = this;
// 返回一个新函数
const boundFunction = function (...newArgs) {
// 判断是否使用 new 调用
const isUsingNew = this instanceof boundFunction;
// 合并预设参数和新传入的参数
const allArgs = [...initialArgs, ...newArgs];
// 如果是使用 new 调用,this 指向新创建的实例
const thisValue = isUsingNew? this : context;
// 调用原始函数
return originalFunction.apply(thisValue, allArgs);
};
// 复制原函数的原型
if (originalFunction.prototype) {
boundFunction.prototype = Object.create(originalFunction.prototype);
}
return boundFunction;
};
核心目标
bind
方法用于创建一个新函数,该函数的 this
值被永久绑定到指定对象,并支持预设部分参数。其实现需解决以下问题:
- 绑定
this
:确保新函数调用时的上下文固定。 - 参数合并:支持预设参数与新参数的拼接(柯里化)。
- 原型链继承:新函数需继承原函数的原型链,且允许
new
操作符调用。
实现步骤详解
1. 校验调用者类型
• 必要性:bind
必须由函数调用,否则抛出错误。
• 实现:检查 this
的类型是否为函数。
if (typeof this !== 'function') {
throw new Error('Function.prototype.bind - not callable');
}
2. 保存原函数与预设参数
• 闭包与柯里化:通过闭包保存原函数(self
)和预设参数(args
),以便后续拼接参数。
const self = this; // 原函数
const args = Array.prototype.slice.call(arguments, 1); // 预设参数
3. 创建新函数并处理调用逻辑
• 动态 this
判断:
• 若通过 new
调用新函数,this
指向新实例(忽略绑定的上下文)。
• 否则,使用绑定的上下文(context
)。
const fBound = function() {
const bindArgs = Array.prototype.slice.call(arguments);
return self.apply(
this instanceof fNOP ? this : context, // new 调用时忽略绑定
args.concat(bindArgs) // 合并预设参数与调用参数
);
};
4. 维护原型链继承
• 中间空函数(fNOP
):避免直接修改原函数的原型链,通过一个空函数继承原函数原型。
const fNOP = function() {
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP(); // 继承原函数原型
5. 返回绑定后的函数
• 最终返回 fBound
,其原型链与原函数一致,且 this
和参数已处理完成。
关键问题与解决方案
1. new
操作符的特殊处理
• 现象:若对 bind
返回的函数执行 new
,原构造函数会被调用,且 this
指向新实例。
• 原理:通过 this instanceof fNOP
判断是否通过 new
调用。
2. 多次 bind
的不可覆盖性
• 表现:多次调用 bind
时,首次绑定的 this
无法被后续 bind
或 call
覆盖。
• 原因:bind
生成的函数内部已固化目标函数和 this
,仅参数可叠加。
3. 原型链污染规避
• 中间函数隔离:通过 fNOP
隔离新函数与原函数的原型,避免直接修改原型的副作用。
Polyfill 实现示例
Function.prototype.myBind = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error');
}
const self = this;
const args = Array.prototype.slice.call(arguments, 1);
const fNOP = function() {
};
const fBound = function() {
return self.apply(
this instanceof fNOP ? this : context,
args.concat(Array.prototype.slice.call(arguments))
);
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
应用场景
- 固定
this
:解决回调函数中的this
丢失问题(如事件处理器)。 - 参数预设:实现函数柯里化,例如预设 API 请求的公共参数。
- 构造函数兼容:允许
bind
生成函数通过new
调用,保持原型链完整性。
总结
bind
的实现核心在于 闭包保存上下文、参数柯里化拼接 和 原型链继承处理。通过中间函数隔离原型、动态判断 new
调用场景,确保其行为与原生方法一致。实际开发中可直接使用原生 bind
,但理解其原理有助于解决高阶函数设计中的疑难问题。
new
function myNew(constructor,...args){
let obj =obj.create(constructor.Prototype);
let result=constructor.apply(obj,args);
return typeof result ===`object`?result:obj;
}
继承实现方式
// 继承的多种方式
/* // 1. 原型链继承
//共享父类实例的属性和方法
function Parent() {
this.name = 'parent'
this.play = [1, 2, 3]
}
Parent.prototype.getName = function() {
console.log(this.name)
}
function Child() {
this.type = 'child'
}
核心
Child.prototype = new Parent();
const child1 = new Child();
child1.play.push(4);
console.log(child1.play); // [1, 2, 3, 4]
const child2 = new Child();
console.log(child2.play); // [1, 2, 3, 4] */
/* // 2. 构造函数继承
//只能继承父类实例的属性和方法,不能继承父类原型的属性和方法
function Parent(name) {
this.name =name
this.play = [1, 2, 3]
}
Parent.prototype.getName = function() {
console.log(this.name)
}
function Child(name) {
Parent.call(this,name)
}
const child = new Child("child1");
child.play.push(4);
console.log(child.play); // [1, 2, 3, 4]
console.log(child.name); // child1
const child2 = new Child("child2");
console.log(child2.name); // child2
console.log(child2.play); // [1, 2, 3]
console.log(child.getName()); // 报错,无法获取原型上方法
*/
/* // 3. 组合继承
//结合了两种模式的优点,但是会调用两次父类构造函数
function Parent(name) {
this.name =name
this.play = [1, 2, 3]
}
Parent.prototype.getName = function() {
console.log(this.name)
}
function Child(name,age) {
Parent.call(this,name)
this.age = age
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child = new Child("child1",18);
child.play.push(4);
console.log(child.play); // [1, 2, 3, 4]
console.log(child.name); // child1
console.log(child.age); // 18
const child2 = new Child("child2",19);
console.log(child2.name); // child2
console.log(child2.age); // 19
console.log(child2.play); // [1, 2, 3] */
/*// 4. 寄生组合继承
//组合继承的优化版,避免了组合继承的缺点 ,只调用一次父类构造函数
function Parent(name) {
this.name =name
this.play = [1, 2, 3]
}
Parent.prototype.getName = function() {
console.log(this.name)
}
function Child(name,age) {
Parent.call(this,name)
this.age = age
}
//new Parent()->Object.create(Parent.prototype)
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
const child = new Child("child1",18);
child.play.push(4);
console.log(child.play); // [1, 2, 3, 4]
console.log(child.name); // child1
console.log(child.age); // 18
const child2 = new Child("child2",19);
console.log(child2.name); // child2
console.log(child2.age); // 19
console.log(child2.play); // [1, 2, 3] */
/*// 5. class继承
class Parent {
constructor(name) {
this.name = name
this.play = [1, 2, 3]
}
getName() {
console.log(this.name)
}
}
class Child extends Parent {
constructor(name,age) {
super(name)
this.age = age
}
}
const child = new Child("child1",18);
child.play.push(4);
console.log(child.play); // [1, 2, 3, 4]
console.log(child.name); // child1
console.log(child.age); // 18
const child2 = new Child("child2",19);
console.log(child2.name); // child2
console.log(child2.age); // 19
console.log(child2.play); // [1, 2, 3] */