ES5 版本 bind 实现中维护构造函数的原因详解
在 ES5 版本的 bind
实现中,需要显式维护构造函数关系,这是为了确保使用 new
操作符调用绑定函数时,能够正确处理原型链和继承关系。
核心问题:原型链的断裂
当使用 new
操作符调用绑定函数时,JavaScript 引擎会创建一个新对象,并将该对象的原型链接到绑定函数的 prototype
属性:
const boundFn = originalFunc.bind(context);
const instance = new boundFn();
在原生 bind
实现中,boundFn
的 prototype
属性会自动设置为与 originalFunc
相同的原型链。但在自定义实现中,我们需要手动维护这个关系。
ES5 实现中的关键代码
// 维护原型链
BoundFn.prototype = Object.create(originalFunc.prototype);
// 修复构造函数指向
BoundFn.prototype.constructor = originalFunc;
为什么需要这两步?
1. 确保 instanceof
操作符正常工作
function Person(name) {
this.name = name;
}
// 使用原生 bind
const BoundPersonNative = Person.bind(null);
const alice = new BoundPersonNative('Alice');
console.log(alice instanceof Person); // true ✅
// 使用自定义 bind(无原型处理)
function brokenBind(fn, context) {
return function() {
return fn.apply(context, arguments);
};
}
const BoundPersonBroken = brokenBind(Person, null);
const bob = new BoundPersonBroken('Bob');
console.log(bob instanceof Person); // false ❌
原因分析:
instanceof
检查右侧构造函数的prototype
是否出现在左侧对象的原型链上- 没有设置
BoundFn.prototype
时,新对象的原型是默认的Object.prototype
- 我们需要手动建立
BoundFn
和originalFunc
的原型连接
2. 保持正确的原型继承
Person.prototype.sayHello = function() {
return `Hello, ${this.name}`;
};
// 正确实现
const BoundPerson = Person.myBind(null);
const carol = new BoundPerson('Carol');
console.log(carol.sayHello()); // "Hello, Carol" ✅
// 缺少原型处理
const BrokenPerson = brokenBind(Person, null);
const dave = new BrokenPerson('Dave');
console.log(dave.sayHello()); // TypeError: dave.sayHello is not a function ❌
原因分析:
- 原型方法定义在
Person.prototype
上 - 新创建的对象需要继承
Person.prototype
- 通过
Object.create(originalFunc.prototype)
建立原型链
3. 修复 constructor
属性
// 正确实现
console.log(carol.constructor === Person); // true ✅
// 没有修复 constructor 的情况
BoundFn.prototype = Object.create(Person.prototype);
console.log(new BoundFn().constructor); // Person ✅ (自动继承)
// 但如果重写整个 prototype
function Animal() {}
BoundFn.prototype = Object.create(Animal.prototype);
console.log(new BoundFn().constructor); // Animal ❌ (错误指向)
原因分析:
- 默认情况下,函数的
prototype.constructor
指向自身 - 当我们使用
Object.create()
时,会创建一个新对象,其constructor
属性继承自原始函数的原型 显式设置
BoundFn.prototype.constructor = originalFunc
确保:- 实例的
constructor
属性正确指向原始构造函数 - 避免意外的继承关系
- 实例的
原型链关系图解
ES5 实现的原型链
ES6+ 版本的原型链
为什么 ES6+ 版本不需要这些?
在 ES6+ 版本的实现中:
if (isNewCall) {
return new originalFunc(...bindArgs, ...callArgs);
}
这里直接使用 new originalFunc()
创建实例,绕过了绑定函数的原型链:
new originalFunc()
直接创建原始构造函数的实例- 实例的原型直接链接到
originalFunc.prototype
- 不涉及绑定函数 (
boundFn
) 的原型
因此 ES6+ 版本不需要维护绑定函数的原型关系。
完整原型链对比
ES5 实现的原型链流程
ES6+ 实现的原型链流程
实际应用场景
需要维护构造函数的场景
- 类库开发:创建兼容旧浏览器的 polyfill
- 框架实现:需要支持 IE11 等老式浏览器
- 自定义继承系统:实现复杂的原型继承逻辑
不需要维护的场景
- 现代浏览器环境:使用原生
bind
- Babel 转译环境:转译器会自动处理
- 无继承的简单函数:不涉及原型链的函数
边界情况处理
1. 原始函数没有 prototype
const arrowFn = () => {};
const boundArrow = arrowFn.myBind(null);
// 应避免设置原型
if (typeof originalFunc.prototype !== 'undefined') {
BoundFn.prototype = Object.create(originalFunc.prototype);
BoundFn.prototype.constructor = originalFunc;
}
2. 原始函数是内置函数
const boundSlice = [].slice.myBind([1,2,3]);
console.log(boundSlice(1)); // [2,3]
// 内置函数通常没有可用的 prototype
// 但仍需正确处理
实现总结
ES5 版本的 bind
实现需要维护构造函数关系,主要是因为:
- 原型链继承:确保新实例继承原始构造函数的原型方法
instanceof
支持:使instanceof
操作符能正确识别实例类型- 构造函数标识:保持实例的
constructor
属性指向正确 - 兼容原生行为:模拟原生
bind
的完整功能
Comments | NOTHING