Позволяет ли спецификация ECMAScript «массив» быть «суперклассируемым»? - PullRequest
0 голосов
/ 05 июля 2018

Я ищу любые признаки того, будет ли "суперклассирование" встроенного типа работать в соответствии со спецификацией . То есть, учитывая любую гипотетически совместимую реализацию ECMAScript, «суперклассирование» встроенного объекта нарушает среду выполнения, влияя на алгоритм создания конструктора класса?

"Superclassable" , термин, который я создаю, относится к классу, чьи объекты, возвращаемые путем его конструирования или вызова его как функции, если это применимо, будут созданы с теми же внутренними слотами (кроме для [[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 Конструктор Array

Конструктор Array:

  • создает и инициализирует новый экзотический объект Array при вызове в качестве конструктора.
  • предназначен для подкласса. Он может использоваться как значение предложения extends определения класса. Конструкторы подкласса, намеревающиеся наследовать экзотическое поведение Array, должны включать супер-вызов конструктора Array для инициализации экземпляров подкласса, являющихся экзотическими объектами Array.

§22.1.3 Свойства объекта-прототипа Array

Объект-прототип Array имеет внутренний слот [[Prototype]], значением которого является внутренний объект% ObjectPrototype%.

Объект-прототип Array указан как экзотический объект Array для обеспечения совместимости с кодом ECMAScript, который был создан до спецификации ECMAScript 2015.

(выделение добавлено)

Насколько я понимаю, соответствующая реализация не требуется для внутреннего вызова super() в конструкторе Array, чтобы правильно инициализировать экземпляр как экзотический массив, и при этом не требует Object быть прямым суперклассом Array (хотя моя первая цитата из §22.1.3 наверняка подразумевает этот бит).

Мой вопрос: работает ли первый приведенный выше фрагмент в соответствии со спецификацией или он работает только потому, что существующие реализации позволяют это сделать? то есть является ли реализация первой HypotheticalArray несоответствующей?

И для получения полной награды я также хотел бы применить этот вопрос к String, Set, Map и TypedArray (под этим я подразумеваю Object.getPrototypeOf(Uint8Array.prototype).constructor).

Я получу 500 бонусных баллов за первый ответ, который строго отвечает на мои вопросы о практике «суперкласса» вышеупомянутых встроенных функций в ECMAScript 2015 и более поздних версиях (черновик, в котором был введен Object.setPrototypeOf()).

Я не собираюсь поддерживать ECMAScript версии 5.1 и ниже, так как изменение встроенной цепочки наследования возможно только путем доступа к __proto__, который является не частью любой спецификации ECMAScript и, следовательно, зависит от реализации.

P.S. Я полностью осознаю причины, по которым такие практики не приветствуются, поэтому я хотел бы определить, допускает ли спецификация «суперклассификацию» без «разрыва сети», как любит говорить TC39.

Ответы [ 2 ]

0 голосов
/ 07 июля 2018

Вызов функции setSuperclassOf в любом встроенном классе ECMAScript не повлияет на поведение конструктора.

Ваш HypotheticalArray конструктор не должен - не должен - вызывать super(). В спецификации вы должны смотреть не только на секцию Конструктор массива , которая дает краткий обзор, но и на подсекции §22.1.1.1 Array ( ) , §22.1.1.2 Array (len) и §22.1.1.3 Array (... items) , которые дают подробные алгоритмы того, что происходит, когда вы вызываете Array (как функцию или конструктор). Они ищут прототип newTarget (чтобы быть подклассифицированным как обычно - начиная с ES6), но они не ищут прототип самой функции Array. Вместо этого все они напрямую отправляют алгоритму ArrayCreate , который просто создает объект, устанавливает его прототип и устанавливает семантику экзотических свойств.

Это похоже на String (который отправляется алгоритму StringCreate при вызове в качестве конструктора), конструктору abstract TypedArray (который просто выдает и явно заявляет, что " Конструкторы TypedArray не выполняют супер-вызов к нему. "), конкретные конструкторы TypedArray (которые отправляют в AllocateTypedArray и IntegerIndexedObjectCreate алгоритмы), а также конструкторы Map и Set (которые оба отправляют алгоритмы OrdinaryCreateFromConstructor и ObjectCreate ). И на самом деле это то же самое для всех других встроенных конструкторов, хотя я не проверял каждый из них в отдельности, их слишком много, как в ES8.

Насколько я понимаю, поскольку Array.prototype сам по себе является экзотическим объектом Array, совместимая реализация не требует внутреннего вызова super() в конструкторе Array, чтобы правильно инициализировать экземпляр как экзотический массив

Нет, это не имеет к этому никакого отношения. Объект не становится экзотическим, потому что он наследуется от экзотического объекта. Объект экзотичен, потому что он был специально создан как таковой. Значение Array.prototype может быть действительно любым, оно не имеет отношения к созданию экземпляров массива - кроме того, оно будет использоваться в качестве прототипа при вызове new Array (в отличие от new ArraySubclass).

Что касается Object.setPrototypeOf(Array.prototype, …), обратите внимание, что Array.prototype даже не является неизменным прототипом экзотического объекта , подобного Object.prototype, так что да, вам разрешено это делать.

0 голосов
/ 07 июля 2018

На основе гарантии, изложенной в §9.3.3 CreateBuiltinFunction ( steps , internalSlotsList [, realm [, prototype ]]) и шаги в §22.1.1 Конструктор массива , невозможный вызов Array(…) или new Array(…) вызовет конструктор объекта или конструктор разрешенного суперкласса массива во время вызова и, следовательно, «суперклассификации», массив гарантированно будет вести себя правильно в любой совместимой реализации ECMAScript 2018.

В связи с открытием §9.3.3, я подозреваю, что тот же вывод будет сделан для остальных классов в текущей спецификации, хотя требуется гораздо больше исследований, чтобы определить, является ли это точным и гарантировано ли это обратно в ECMAScript 2015.

Это не полный ответ, и поэтому я не буду принимать его. Награда все равно будет вознаграждена за полный ответ, независимо от того, будет ли она предоставлена ​​до того, как мой вопрос будет иметь право на получение награды.

...