Я ищу какие-либо признаки того, будет ли суперклассирование встроенного типа работать в соответствии со спецификацией. То есть, учитывая любую гипотетическую совместимую реализацию 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< /а>:
Конструктор массива:
- создает и инициализирует новый экзотический объект Array при вызове в качестве конструктора.
- предназначен для подклассов. Его можно использовать как значение предложения extends определения класса. Конструкторы подклассов, которые намереваются наследовать экзотическое поведение Array, должны включать супервызов конструктора Array для инициализации экземпляров подкласса, которые являются экзотическими объектами Array.
§22.1.3 Свойства массива Объект-прототип
У объекта-прототипа 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.
Object.getPrototypeOf(Array.prototype)
будетObject.prototype
(для чего нет веских причин ожидать этого), никто не может разумно ожидать, что это нарушит какие-либо существующие основы кода. Есть разница между ожиданием наследования (которое я не нарушаю) и ожиданием прямого суперкласса (которое, как я утверждаю, здесь является необоснованным ожиданием). - person Patrick Roberts   schedule 05.07.2018class Test extends Object {}
, результирующий класс требует вызоваsuper()
вconstructor()
независимо от того, определен лиconstructor
явно в определении или нет, потому что суперклассирование его с помощьюclass Verbose { constructor () { console.log('called super()') } }
приведет к созданию журнала консоли в любое время.Test
построен. Однако при объявленииclass Test {}
и суперклассировании его с помощьюVerbose
вызов super отсутствует. Гарантируется ли такое поведение спецификацией? Потому что это, кажется, напрямую связано с моим вопросом. - person Patrick Roberts   schedule 07.07.2018Object.setPrototypeOf(Array, Enumerable)
позволяет конструкторамsuper
указывать на перечисление? - person Jonas Wilms   schedule 07.07.2018Array
никогда не вызываетsuper()
в соответствии со спецификацией, но для класса, чей [[ConstructorKind]] является производным, я говорю да, это изменит то, на что ссылаетсяsuper
, поэтому производный класс не является суперклассом. - person Patrick Roberts   schedule 07.07.2018constructor (...args) { super(...args) }
) или он пересылает такие аргументы в дополнение к своей собственной специфичной для класса инициализации. - person Patrick Roberts   schedule 07.07.2018