Array 的关键在于真正的数组对象是Array Exotic Object。奇异对象是具有使用标准 JS 语言功能无法实现的行为的对象,尽管在 ES6 中Proxy 允许用户代码更多地创建类似奇异对象的能力。
当对返回像Array 这样的奇异对象的构造函数进行子类化时,子类化方法需要以这样一种方式完成,即创建的对象实际上是一个奇异对象。当你做类似的事情时
function ArraySubclass(){}
ArraySubclass.prototype = Object.create(Array.prototype);
然后
(new ArraySubclass()) instanceof Array
因为原型匹配,但new ArraySubclass 返回的对象只是一个普通对象,其原型链中恰好有Array.prototype。但你会注意到
Array.isArray(new ArraySubclass()); // false
因为该对象不是真正的异国情调。在这种情况下
new ArraySubclass()
等同于做
var obj = Object.create(ArraySubclass.prototype);
ArraySubclass.call(obj);
那么在 ES5 中你如何扩展Array?您需要创建一个奇异对象,但您还需要确保该奇异对象在其原型链中有您的ArraySubclass.prototype 对象。这就是 ES5 遇到的问题,因为在原版 ES5 中,无法更改现有对象的原型。使用许多引擎添加的__proto__ 扩展,您可以使用类似的代码获得正确的数组子类化行为
var obj = new Array();
obj.__proto__ = ArraySubclass.prototype;
ArraySubclass.call(obj);
假设你想概括上面的模式,你会怎么做?
function makeSubclass(baseConstructor, childConstructor){
var obj = new baseConstructor();
obj.__proto__ = childConstructor.prototype;
return obj;
}
function ArraySubclass(){
var arr = makeSubclass(Array, ArraySubclass);
// do initialization stuff and use 'arr' like 'this'
return arr;
}
ArraySubclass.prototype = Object.create(Array.prototype);
所以这在 ES5 + __proto__ 中有效,但是随着事情变得更加复杂怎么办?如果你想继承ArraySubclass 怎么办?您必须能够更改makeSubclass 的第二个第二个参数。但是我们该怎么做呢?这里的实际目标是什么?当你做类似的事情时
new ArraySubclass()
传递给new 的值是我们关心的第二个参数,并且应该传递的是构造函数的原型。在 ES5 中没有很好的途径来实现这一点。
这就是 ES6 类的优势所在。
class ArraySubclass extends Array {
constructor(){
super();
}
}
关键是当super()运行时,它知道ArraySubclass是子类。当super()调用new Array时,它还传递了一个额外的隐藏参数,上面写着“嘿,当你创建这个数组时,将它的原型设置为ArraySubclass.prototype。如果有很多层次的继承,它会传递孩子- 大多数原型,以便返回的奇异对象是真正的奇异对象,同时还要确保它具有正确的原型。
这不仅意味着事物构造正确,而且意味着引擎可以预先创建具有正确原型值的对象。由于引擎处理和跟踪对象的方式,在创建后更改对象的 __proto__ 值是众所周知的去优化点。