Я ищу любые признаки того, будет ли "суперклассирование" встроенного типа работать в соответствии со спецификацией . То есть, учитывая любую гипотетически совместимую реализацию 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.