问题描述
今天在研究JS中bind polyfill实现的时候碰到一个问题:
Function.prototype.myBind = function (obj) {
// 检查被绑定的是否为函数(假设此例为foo)
if (typeof this !== 'function') {
throw new TypeError('not a function');
}
// 将foo传给that保存
var that = this,
// 取出传入的参数
oldArr = Array.prototype.slice.call(arguments, 1),
fnVoid = function () {}, // 定义一个空函数用于之后的原型链绑定
fnNew = function () {
return that.apply(this instanceof fnVoid && obj ? this : obj, [
// 判断this的指向,如果是使用new调用,则this绑定到新对象,此时新对象的原型为构造函数,即this instanceof fnVoid
// 此时需要将新对象this传入that(foo)函数,将构造函数的属性绑定到新对象上
// 如果不是使用new调用,则传入obj,将foo函数绑定到传入的obj上
...oldArr,
...arguments,
]);
};
// 为什么不直接 fnNew.prototype = new this(), 而要借用一个空函数呢
fnVoid.prototype = this.prototype;
fnNew.prototype = new fnVoid();
return fnNew;
};
如最后一个注释所述,为什么不直接 fnNew.prototype = new this(), 而要借用一个空函数呢?这样不是也能实现对原函数的继承吗?
还有就是突然想到之前看到的原型式继承,为什么子类的原型要继承至父类的一个实例,而不是直接继承至父类的原型呢?
function TypeA(name) {
this.name = name;
}
function TypeB(age) {
this.age = age;
}
TypeB.prototype = new TypeA();
小白刚入门,对很多问题的理解不是很深,感谢各位大佬能点拨点拨,感激不尽!!!!
尝试答一下。
乍一看我也觉得奇怪,疑点有二:
1. 我的第一直觉是应该这么干:
判断 new 调用使用 this instanceof fnNew, fnNew.prototype = this.prototype, 直接不需要 fnVoid 的存在:
妥妥的不是么?有问题吗?等等,确实是有的。
问题是返回的函数的 prototype 引用了原先函数的 prototype,意味着你如果添加新属性则会影响旧函数。
2. 所以我们避免直接引用原先函数的 prototype
fnVoid 的作用就是用来当中间层用,那么不用 fnVoid,用 new this() 可以么?
试试:
你看, new this 是有副作用的,它会导致意外执行原函数本身。
3. 引入的 fnVoid 其实等价于 Object.create 的 polyfill
要我说,这个地方不引入 fnVoid 也是可以的,我们的目的其实就是让 fnNew 能间接引用到 原函数的 prototype.
而 Object.create 的 polyfill 版本就是:
这就是 fnVoid 的由来。空函数是为了搭配 new 使用继承 prototype 并且不产生副作用。本质和 ES6 的 Object.create 或者 Object.setPrototypeOf 是一个作用。
关于第二个原型问题,我也一并解释一下,篇幅比较长。
我在这里先声明一下几个术语,前端届对原型的称呼一直比较混乱:
原型:
点操作符
或者[ ]
访问属性时,会先检查对象本身的属性,如果不存在则会检查对象的原型,如果还不存在则会继续检查对象原型的原型直到原型的尽头 Object.prototype ,它没有原型,它的原型是 null,这个就是所谓的原型链。new 一个构造函数/类的时候发生了什么:
什么是继承?
基于 prototype 的继承模拟基于 class 的继承:
回到你的例子,
TypeB.prototype = new TypeA();
这一句其实是错误的,这里存在一个非预期的副作用,那就是 TypeA 的构造器莫名被调用了一次。这里其实是需要使用 Object.create 或者其 polyfill 即空函数+new 来实现的。