【问题标题】:Why inheriting from Array is difficult to implement in ES5?为什么从 Array 继承在 ES5 中难以实现?
【发布时间】:2016-12-05 03:54:11
【问题描述】:

使用 ES5 中的原型继承,从 Array 继承并获得预期的行为似乎并非易事,例如在将项目添加到 Array 时自动更新 .length(参见下面的代码)。 ES5 创建派生函数的对象 (MyArray) 然后将其传递基类型进行初始化,为什么这个模型很难在这个模型中得到预期的行为?

ES6改变了基础构造函数的行为和创建对象,然后派生类的构造函数初始化它(在调用super()之后),想知道为什么这解决了问题。

function MyArray(){}
MyArray.prototype = Object.create(Array.prototype);

var myArr = new MyArray();
myArr[0] = 'first';
console.log(myArr.length); // expect '1', but got '0' in output

【问题讨论】:

  • ES5 不支持从 Array 继承这种方式(只是他们没有放入 ES5 的东西)。当您尝试时,您会失去 .length 所具有的特殊功能,因此它不再正常工作。 ES6 具有继承 Array 的能力。
  • “想知道为什么这解决了问题” 因为超级构造函数 (Array) 创建了具有所有特殊内部行为的对象,而不是子构造函数。跨度>

标签: javascript ecmascript-6


【解决方案1】:

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__ 值是众所周知的去优化点。

【讨论】:

  • 是孩子最多的构造函数new.target吗?如果是这样,'new.target` 使隐式隐藏参数变得容易吗?
  • 是的,在这种情况下,函数声明中的new.target 本质上是您想要的,但是如果您使用标准函数声明方法,您仍然需要一种正确构造对象的方法并且调用父构造函数,这并不容易。
猜你喜欢
  • 2020-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-03
  • 2018-02-14
  • 1970-01-01
  • 2011-06-12
相关资源
最近更新 更多