ECMAScript规范是否允许Array是“超类的"? [英] Does the ECMAScript specification allow Array to be "superclassable"?

查看:87
本文介绍了ECMAScript规范是否允许Array是“超类的"?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm looking for any indications whether or not "superclassing" a builtin type will work according to the specification. That is, given any hypothetical conformant implementation of ECMAScript, does "superclassing" a builtin break the runtime by affecting the creation algorithm of the class constructor?

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

"Superclassable", a term I'm coining, refers to a class whose objects returned by constructing it, or calling it as a function if applicable, will be created with the same internal slots (except for [[Prototype]]), regardless of what its direct superclass is, as long as the initial [[Prototype]] of the class constructor and the class prototype are still in each respective inheritance chain after reassigning them. Consequently, in order to be "superclassable", a class must not call super() during creation.

超类化" Array时,我希望它看起来像这样:

When "superclassing" an Array, I would expect it to look something like this:

// 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 not 超类":

One of my biggest concerns is that internally, in a possibly conformant implementation, Array could potentially look like this, which would mean that Array is not "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)
}

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

However, Array is "superclassable" if a conformant implementation is required not to call 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 :

With that in mind, I'd like to reference a few points from the current draft, ECMAScript 2018:

§22.1.1数组构造器

Array构造函数:

The Array constructor:

  • 作为构造函数调用时会创建并初始化一个新的Array奇异对象.
  • 被设计为可归类的.它可以用作类定义的extends子句的值. 打算继承奇异Array行为的子类构造函数必须包括对Array构造函数的超级调用,以初始化作为Array奇异对象的子类实例.
  • creates and initializes a new Array exotic object when called as a constructor.
  • is designed to be subclassable. It may be used as the value of an extends clause of a class definition. Subclass constructors that intend to inherit the exotic Array behaviour must include a super call to the Array constructor to initialize subclass instances that are Array exotic objects.

§22.1.3数组原型对象的属性

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

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

The Array prototype object is specified to be an Array exotic object to ensure compatibility with ECMAScript code that was created prior to the ECMAScript 2015 specification.

(强调已添加)

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

My understanding is that a conformant implementation is not required to internally call super() within the Array constructor in order to properly initialize the instance as an array exotic, nor does it require Object to be the direct superclass of Array (though my first quote of §22.1.3 certainly seems to imply that bit).

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

My question is, does the first snippet above work according to the specification, or does it only work because the currently existing implementations allow it to? i.e. is the implementation of the first HypotheticalArray non-conformant?

为了获得全额奖励,我还想将此问题应用于StringSetMapTypedArray(我指的是Object.getPrototypeOf(Uint8Array.prototype).constructor).

And for full bounty award, I'd also like to apply this question to String, Set, Map, and TypedArray (by which I mean Object.getPrototypeOf(Uint8Array.prototype).constructor).

对于将严格解决我对ECMAScript 2015及更高版本中的内置函数进行超分类"的问题的第一个答案,我将奖励500奖励积分(引入了Object.setPrototypeOf()的草案).

I will award 500 bounty points for the first answer that rigorously addresses my questions about the practice of "superclassing" the above builtins in ECMAScript 2015 and up (the draft in which Object.setPrototypeOf() was introduced).

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

I do not intend to support ECMAScript edition 5.1 and below, as modifying a builtin's inheritance chain is only possible by accessing __proto__, which is not part of any ECMAScript specification and is therefore implementation-dependent.

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

推荐答案

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

Calling your setSuperclassOf function on any ECMAScript builtin class will not affect the behaviour of the constructor.

您的HypotheticalArray构造函数不应-不得-调用super().在规范中,您不应该只看 The数组构造器部分,其中提供了简短概述,但也可以在 newTarget 的原型(从ES6开始就可以照常归类),但是他们没有查询Array函数本身的原型.相反,它们都直接直接调度到 ArrayCreate 仅创建对象的算法,设置其原型并安装奇特的属性语义.

Your HypotheticalArray constructor should not - must not - call super(). In the spec, you should not just look at the The Array Constructor section which gives a short overview, but also at the subsections §22.1.1.1 Array(), §22.1.1.2 Array(len) and §22.1.1.3 Array(...items) which give the detailed algorithms of what happens when you call Array (as either a function or constructor). They do look up the prototype of the newTarget (to be subclassable as usual - since ES6), but they do not look up the prototype of the Array function itself. Instead, they all do directly dispatch to the ArrayCreate algorithm, which just creates an object, sets up its prototype and installs the exotic property semantics.

对于 String (将其分配给 StringCreate 算法作为构造函数调用时),抽象TypedArray构造函数(它只是抛出并明确指出" TypedArray构造函数不会对其执行超级调用."), Map Set 构造函数(它们都分派给 OrdinaryCreateFromConstructor ObjectCreate 算法).而且afaik对于所有其他内置构造函数也一样,尽管我没有单独检查每个构造函数,但ES8的数量太多了.

It's similar for String (which dispatches to the StringCreate algorithm when called as a constructor), the abstract TypedArray constructor (which just throws and explicitly states that "The TypedArray constructors do not perform a super call to it."), the concrete TypedArray constructors (which dispatch to the AllocateTypedArray and IntegerIndexedObjectCreate algorithms), and the Map and Set constructors (which both dispatch to the OrdinaryCreateFromConstructor and ObjectCreate algorithms). And afaik it's the same for all other builtin constructors as well, though I haven't checked each of them individually, there are just too many as of ES8.

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

My understanding is that because Array.prototype itself is an Array exotic object, a compliant implementation is not required to internally call super() within the Array constructor in order to properly initialize the instance as an array exotic

不,这与它无关.一个对象不会成为异质对象,因为它是从异质对象继承而来的.对象是奇异的,因为它是这样专门创建的. Array.prototype的值实际上可以是任何东西,与数组实例的创建无关-除了在调用new Array时将其用作原型(与new ArraySubclass相比).

No, that has nothing to do with it. An object doesn't become exotic because it inherits from an exotic object. An object is exotic because it was specifically created as such. The value of Array.prototype can be really anything, it's not relevant for the creation of array instances - apart from that it'll be used as the prototype when new Array is called (in contrast to new ArraySubclass).

关于Object.setPrototypeOf(Array.prototype, …),请注意Array.prototype甚至不是像Object.prototype这样的不可变的原型奇特物体,所以是的,你被允许这样做.

Regarding Object.setPrototypeOf(Array.prototype, …), notice that Array.prototype is not even an immutable prototype exotic object like Object.prototype, so yes you are allowed to do that.

这篇关于ECMAScript规范是否允许Array是“超类的"?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆