ECMAScript规范是否允许Array是“超类”的?

时间:2018-07-05 18:17:17

标签: javascript arrays ecmascript-6 language-lawyer prototypal-inheritance

我正在寻找是否有任何迹象表明“超类化”内置类型是否可以符合规范。 。也就是说,给定ECMAScript的任何假设一致的实现,内置的“超类化”是否会通过影响类构造函数的创建算法来破坏运行时间?

“超类” ,我是用一个术语来指代的类,该类的构造,构造或通过调用将其返回的对象(如果适用)将使用相同的内部插槽创建(除了[[Prototype]]之外,无论其直接超类是什么,只要在重新分配它们后,类构造函数的初始[[Prototype]]和类原型仍在各自的继承链中。因此,为了成为“超类”,类在创建过程中不得调用 super()

当“超类” Array时,我希望它看起来像这样:

// clearly this would break Array if the specification allowed an implementation
// to invoke super() internally in the Array constructor
class Enumerable {
  constructor (iterator = function * () {}) {
    this[Symbol.iterator] = iterator
  }

  asEnumerable() {
    return new Enumerable(this[Symbol.iterator].bind(this))
  }
}

function setSuperclassOf (Class, Superclass) {
  /* These conditions must be satisfied in order to
   * superclass Class with Superclass
   */
  if (
    !(Superclass.prototype instanceof Object.getPrototypeOf(Class.prototype).constructor) ||
    !(Superclass instanceof Object.getPrototypeOf(Class).constructor) ||
     (Superclass.prototype instanceof Class)
  ) {
    throw new TypeError(`${Class.name} cannot have their superclass set to ${Superclass.name}`)
  }
  
  // Now we can superclass Class with Superclass
  Object.setPrototypeOf(Class.prototype, Superclass.prototype)
  Object.setPrototypeOf(Class, Superclass)
}

setSuperclassOf(Array, Enumerable)

const array = new Array(...'abc')

// Checking that Array is not broken by Enumerable
console.log(array[Symbol.iterator] === Array.prototype[Symbol.iterator])

// Checking that Enumerable works as expected
const enumerable = array.asEnumerable()

console.log(array instanceof Enumerable)
console.log(!(enumerable instanceof Array))

for (const letter of enumerable) {
  console.log(letter)
}

我最大的担忧之一是,在内部,在一个可能一致的实现中,Array 可能可能看起来像这样,这意味着Array不是 “超类”:

class HypotheticalArray extends Object {
  constructor (...values) {
    const [value] = values

    // this reference would be modified by superclassing HypotheticalArray
    super()

    if (values.length === 1) {
      if (typeof value === 'number') {
        if (value !== Math.floor(value) || value < 0) {
          throw new RangeError('Invalid array length')
        }

        this.length = value
        return
      }
    }
    
    this.length = values.length

    for (let i = 0; i < values.length; i++) {
      this[i] = values[i]
    }
  }
  
  * [Symbol.iterator] () {
    const { length } = this

    for (let i = 0; i < length; i++) {
      yield this[i]
    }
  }
}

// Array constructor actually inherits from Function prototype, not Object constructor
Object.setPrototypeOf(HypotheticalArray, Object.getPrototypeOf(Function))

class Enumerable {
  constructor (iterator = function * () {}) {
    this[Symbol.iterator] = iterator
  }

  asEnumerable() {
    return new Enumerable(this[Symbol.iterator].bind(this))
  }
}

function setSuperclassOf (Class, Superclass) {
  /* These conditions must be satisfied in order to
   * superclass Class with Superclass
   */
  if (
    !(Superclass.prototype instanceof Object.getPrototypeOf(Class.prototype).constructor) ||
    !(Superclass instanceof Object.getPrototypeOf(Class).constructor) ||
     (Superclass.prototype instanceof Class)
  ) {
    throw new TypeError(`${Class.name} cannot have their superclass set to ${Superclass.name}`)
  }
  
  // Now we can superclass Class with Superclass
  Object.setPrototypeOf(Class.prototype, Superclass.prototype)
  Object.setPrototypeOf(Class, Superclass)
}

setSuperclassOf(HypotheticalArray, Enumerable)

const array = new HypotheticalArray(...'abc')

// Array is broken by Enumerable
console.log(array[Symbol.iterator] === HypotheticalArray.prototype[Symbol.iterator])

// Checking if Enumerable works as expected
const enumerable = array.asEnumerable()

console.log(array instanceof Enumerable)
console.log(!(enumerable instanceof HypotheticalArray))

// Iteration does not work as expected
for (const letter of enumerable) {
  console.log(letter)
}

但是,如果需要一致的实现来调用Array,则super() “超类”:

class HypotheticalArray {
  constructor (...values) {
    const [value] = values

    // doesn't ever invoke the superclass constructor
    // super()

    if (values.length === 1) {
      if (typeof value === 'number') {
        if (value !== Math.floor(value) || value < 0) {
          throw new RangeError('Invalid array length')
        }

        this.length = value
        return
      }
    }
    
    this.length = values.length

    for (let i = 0; i < values.length; i++) {
      this[i] = values[i]
    }
  }
  
  * [Symbol.iterator] () {
    const { length } = this

    for (let i = 0; i < length; i++) {
      yield this[i]
    }
  }
}

class Enumerable {
  constructor (iterator = function * () {}) {
    this[Symbol.iterator] = iterator
  }

  asEnumerable() {
    return new Enumerable(this[Symbol.iterator].bind(this))
  }
}

function setSuperclassOf (Class, Superclass) {
  /* These conditions must be satisfied in order to
   * superclass Class with Superclass
   */
  if (
    !(Superclass.prototype instanceof Object.getPrototypeOf(Class.prototype).constructor) ||
    !(Superclass instanceof Object.getPrototypeOf(Class).constructor) ||
     (Superclass.prototype instanceof Class)
  ) {
    throw new TypeError(`${Class.name} cannot have their superclass set to ${Superclass.name}`)
  }
  
  // Now we can superclass Class with Superclass
  Object.setPrototypeOf(Class.prototype, Superclass.prototype)
  Object.setPrototypeOf(Class, Superclass)
}

setSuperclassOf(HypotheticalArray, Enumerable)

const array = new HypotheticalArray(...'abc')

// Array is not broken by Enumerable
console.log(array[Symbol.iterator] === HypotheticalArray.prototype[Symbol.iterator])

// Checking if Enumerable works as expected
const enumerable = array.asEnumerable()

console.log(array instanceof Enumerable)
console.log(!(enumerable instanceof HypotheticalArray))

// Iteration works as expected
for (const letter of enumerable) {
  console.log(letter)
}

考虑到这一点,我想参考当前草案ECMAScript 2018中的几点:

  

§22.1.1 The Array Constructor

     

Array构造函数:

     
      
  • 作为构造函数调用时会创建并初始化一个新的Array奇异对象
  •   
  • 被设计为可归类的。它可以用作类定义的extends子句的值。 打算继承奇异Array行为的子类构造函数必须包括对Array构造函数的超级调用,以初始化作为Array奇异对象的子类实例。
  •   
     

§22.1.3 Properties of the Array Prototype Object

     

Array原型对象具有一个[[Prototype]]内部插槽,其值是固有对象%ObjectPrototype%。

     
    

将Array原型对象指定为Array奇异对象,以确保与ECMAScript 2015规范之前创建的ECMAScript代码兼容。

  
     

(添加了重点)

我的理解是,不需要使用一致的实现来在super()构造函数内内部调用Array以便正确地将实例初始化为一个奇异的数组,也不需要它要求ObjectArray的直接超类(尽管我对§22.1.3的第一句肯定暗示了这一点)。

我的问题是,上面的第一个代码段是否按照规范工作,还是仅因为当前现有的实现允许它工作?即是第一个HypotheticalArray不一致的实现?

为了获得全额奖励,我还想将此问题应用于StringSetMapTypedArray(我的意思是{{ 1}})。

对于将严格解决我有关在ECMAScript 2015及更高版本中对上述内置函数进行“超分类”的问题的第一个答案,我将奖励500奖励积分(其中Object.getPrototypeOf(Uint8Array.prototype).constructor被介绍了。)

我不打算支持ECMAScript 5.1及更低版本,因为只能通过访问Object.setPrototypeOf()来修改内置的继承链,__proto__ any 的一部分 em> ECMAScript规范,因此与实现有关。

P.S。我完全知道不鼓励使用此类做法的原因,这就是为什么我想确定该规范是否允许“超类”而不“破坏网络”(如TC39喜欢说的那样)。

2 个答案:

答案 0 :(得分:3)

根据§9.3.3 CreateBuiltinFunction ( steps, internalSlotsList [ , realm [ , prototype ] ] )中概述的保证和§22.1.1 The Array Constructor中的步骤,对select from classDetails where standard like "Class [1-3]" Array(…)的可能调用将不会调用Object构造函数或Array的构造函数可以在调用时解决超类,因此可以保证“超类”数组在ECMAScript 2018的任何符合实现中都能正常运行。

由于发现了第9.3.3节,我怀疑对于当前规范中的其余类也会得出相同的结论,尽管还需要进行大量研究来确定这是否正确,并保证可以返回ECMAScript。 2015。

这不是完整的答案,因此我不会接受。不论是否在我的问题有资格得到赏金之前就提供赏金,赏金仍将获得完整答案。

答案 1 :(得分:3)

在任何ECMAScript内置类上调用setSuperclassOf函数不会影响构造函数的行为。

您的HypotheticalArray构造函数不应-不得-调用super()。在规范中,您不仅应该查看提供简短概述的The Array Constructor section,还应该查看§22.1.1.1 Array()§22.1.1.2 Array(len)§22.1.1.3 Array(...items)的小节,其中详细介绍了调用Array(作为函数或构造函数)时会发生什么。他们确实查询了newTarget的原型(从ES6开始,可以照常被子类化),但是他们没有查询Array函数本身的原型。相反,它们都直接分配给ArrayCreate algorithm,后者只是创建一个对象,设置其原型并安装奇异属性语义。

对于String(作为构造函数调用时将调度到StringCreate algorithm),abstract TypedArray constructor(仅抛出并明确声明“ TypedArray构造函数执行“),concrete TypedArray constructors(调度到AllocateTypedArrayIntegerIndexedObjectCreate算法)以及Map和{{ 3}}构造函数(都分派给SetOrdinaryCreateFromConstructor算法)。而且afaik对于所有其他内置构造函数也一样,尽管我没有单独检查每个构造函数,但ES8的数量太多了。

  

我的理解是,由于Array.prototype本身是一个数组奇异对象,因此不需要遵从实现就可以在super()构造函数中内部调用Array来正确初始化实例为异国情调的数组

不,这与它无关。一个对象不会成为异质对象,因为它继承自异质对象。对象是奇异的,因为它是这样专门创建的。 Array.prototype的值实际上可以是任何值,它与数组实例的创建无关-除此之外,在调用new Array时它将用作原型(与{{1}相反) }。

关于new ArraySubclass,请注意Object.setPrototypeOf(Array.prototype, …)甚至不像Array.prototype那样的ObjectCreate,所以可以的。