首页 文章

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

提问于
浏览
15

我正在寻找任何指示是否"superclassing"内置类型将根据规范工作 . 也就是说,假设任何假设符合ECMAScript的实现,内置函数是否通过影响类构造函数的创建算法来破坏运行时?

"Superclassable",我正在创造的一个术语,指的是一个类,通过构造它来返回它的对象,或者如果适用的话,它将作为一个函数调用它,将使用相同的内部插槽创建([[Prototype]]除外),无论是什么它的直接超类是,只要在重新分配它们之后,类构造函数和类原型的初始[[Prototype]]仍然在每个相应的继承链中 . 因此,为了成为"superclassable",类在创建期间不得调用 super() .

当"superclassing" 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 不是"superclassable":

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 是"superclassable"如果要求符合要求的实现不要调用 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.1Array构造函数Array构造函数:在作为构造函数调用时创建并初始化一个新的Array外来对象 . 被设计为可子类化 . 它可以用作类定义的extends子句的值 . 打算继承奇异数组行为的子类构造函数必须包含对Array构造函数的超级调用,以初始化作为Array外来对象的子类实例 . §22.1.3数组原型对象的属性数组原型对象有一个[[Prototype]]内部插槽,其值是内部对象%ObjectPrototype% . Array原型对象被指定为Array外来对象,以确保与ECMAScript 2015规范之前创建的ECMAScript代码兼容 . (重点补充)

我的理解是,在 Array 构造函数内部调用 super() 不需要一致的实现,以便将实例正确地初始化为异常数组,也不要求 Object 成为 Array 的直接超类(尽管我的第一个引用§22.1) .3当然似乎暗示那一点) .

我的问题是,上面的第一个片段是否按照规范工作,或者它是否只能工作,因为当前现有的实现允许它?即第一个 HypotheticalArray 不符合的实施?

而对于全额赏金奖励,我也想将此问题应用于 StringSetMapTypedArray (我的意思是 Object.getPrototypeOf(Uint8Array.prototype).constructor ) .

我将为第一个答案奖励500个赏金点,这个答案严格地解决了我在ECMAScript 2015及其中的上述内容的实践问题(引入了 Object.setPrototypeOf() 的草案) .

我不打算支持ECMAScript 5.1及更低版本,因为修改内置的继承链只能通过访问 __proto__ 来实现,而 __proto__ 不是任何ECMAScript规范的一部分,因此与实现有关 .

附:我完全清楚不鼓励这样做的原因,这就是为什么我想确定规范是否允许“超级分类”而不是“打破网络”,正如TC39喜欢说的那样 .

2 回答

  • 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算法),以及MapSet构造函数(它们都调度到OrdinaryCreateFromConstructorObjectCreate算法) . 并且afaik它's the same for all other builtin constructors as well, though I haven' t单独检查它们,有太多的ES8 .

    我的理解是因为Array.prototype本身是一个Array异域对象,内部调用super()不需要兼容的实现在Array构造函数中,以便将实例正确初始化为异常数组

    不,这与它无关 . 对象不会变得异国情调,因为它继承了异国情调的对象 . 对象是异国情调的,因为它是专门创建的 . Array.prototype 的值可以是任何东西,当 new Array 被调用时,它将被用作原型(与 new ArraySubclass 形成对比) .

    关于 Object.setPrototypeOf(Array.prototype, …) ,请注意 Array.prototype 甚至不是immutable prototype exotic object,如 Object.prototype ,所以是的,你被允许这样做 .

  • 3

    基于§9.3.3 CreateBuiltinFunction ( steps, internalSlotsList [ , realm [ , prototype ] ] )中概述的保证和§22.1.1 The Array Constructor中的步骤, Array(…)new Array(…) 的任何可能的调用都不会在调用时调用Object构造函数或Array的已解析超类的构造函数,因此保证"superclassing" Array在任何符合ECMAScript 2018的实现 .

    由于§9.3.3的发现,我怀疑当前规范中的其余类将得出相同的结论,尽管需要更多的研究来确定这是否准确,并保证回到ECMAScript 2015 .

    这不是一个完整的答案,因此我不会接受它 . 无论是否在我的问题有资格获得赏金之前提供奖励,赏金仍将被奖励为完整答案 .

相关问题