尝试答一下。
乍一看我也觉得奇怪,疑点有二:
- 为什么判断 bind 返回的函数是否通过 new 调用要用 this instanceof fnVoid
- 为什么引入 fnVoid 然后弄一堆 prototype 赋值
1. 我的第一直觉是应该这么干:
判断 new 调用使用 this instanceof fnNew, fnNew.prototype = this.prototype, 直接不需要 fnVoid 的存在:
Function.prototype.myBind = function (obj) {
// ... 省略
var fnNew = function () {
return that.apply(this instanceof fnNew && obj ? this : obj, [
...oldArr,
...arguments,
]);
};
fnNew.prototype = this.prototype;
return fnNew;
};
妥妥的不是么?有问题吗?等等,确实是有的。
问题是返回的函数的 prototype 引用了原先函数的 prototype,意味着你如果添加新属性则会影响旧函数。
// 你定义了一个 A
function A(){}
A.prototype.a = function(){return 1;}
// 另一个人干了这件事
var B = A.myBind();
B.prototype.a = function(){return 2;}
var a = new A();
a.a(); // 见鬼,谁把老子的函数改了
2. 所以我们避免直接引用原先函数的 prototype
fnVoid 的作用就是用来当中间层用,那么不用 fnVoid,用 new this() 可以么?
试试:
Function.prototype.myBind = function (obj) {
// ... 省略
var fnNew = function () {
return that.apply(this instanceof fnNew && obj ? this : obj, [
...oldArr,
...arguments,
]);
};
fnNew.prototype = new this();
return fnNew;
};
function A(){ alert(1); }
var B = A.bind({});
// 还没干什么呢,A 就被执行了一遍?
你看, new this 是有副作用的,它会导致意外执行原函数本身。
3. 引入的 fnVoid 其实等价于 Object.create 的 polyfill
要我说,这个地方不引入 fnVoid 也是可以的,我们的目的其实就是让 fnNew 能间接引用到 原函数的 prototype.
Function.prototype.myBind = function (obj) {
// ... 省略
var fnNew = function () {
return that.apply(this instanceof fnNew && obj ? this : obj, [
...oldArr,
...arguments,
]);
};
fnNew.prototype = Object.create(this.prototype);
return fnNew;
};
而 Object.create 的 polyfill 版本就是:
Object.create = function(o) {
function F() {}
F.prototype = o;
return new F();
};
这就是 fnVoid 的由来。空函数是为了搭配 new 使用继承 prototype 并且不产生副作用。本质和 ES6 的 Object.create 或者 Object.setPrototypeOf 是一个作用。
关于第二个原型问题,我也一并解释一下,篇幅比较长。
我在这里先声明一下几个术语,前端届对原型的称呼一直比较混乱:
回到你的例子, TypeB.prototype = new TypeA();
这一句其实是错误的,这里存在一个非预期的副作用,那就是 TypeA 的构造器莫名被调用了一次。这里其实是需要使用 Object.create 或者其 polyfill 即空函数+new 来实现的。
function TypeA(name) {
this.name = name;
}
function TypeB(age) {
this.age = age;
}
TypeB.prototype = new TypeA(); // 错误
TypeB.prototype = Object.create(TypeA.prototype);//
// 同时 TypeB 的原型要指向 TypeA 以便继承静态方法
Object.setPrototypeOf(TypeB, TypeA);
TypeA.staticP = 1;
console.log(TypeB.staticP); // 1