我正在寻找是否有任何迹象表明“超类化”内置类型是否可以符合规范。 。也就是说,给定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构造函数:
- 作为构造函数调用时会创建并初始化一个新的Array奇异对象。
- 被设计为可归类的。它可以用作类定义的extends子句的值。 打算继承奇异Array行为的子类构造函数必须包括对Array构造函数的超级调用,以初始化作为Array奇异对象的子类实例。
§22.1.3 Properties of the Array Prototype Object
Array原型对象具有一个[[Prototype]]内部插槽,其值是固有对象%ObjectPrototype%。
将Array原型对象指定为Array奇异对象,以确保与ECMAScript 2015规范之前创建的ECMAScript代码兼容。
(添加了重点)
我的理解是,不需要使用一致的实现来在super()
构造函数内内部调用Array
以便正确地将实例初始化为一个奇异的数组,也不需要它要求Object
是Array
的直接超类(尽管我对§22.1.3的第一句肯定暗示了这一点)。
我的问题是,上面的第一个代码段是否按照规范工作,还是仅因为当前现有的实现允许它工作?即是第一个HypotheticalArray
不一致的实现?
为了获得全额奖励,我还想将此问题应用于String
,Set
,Map
和TypedArray
(我的意思是{{ 1}})。
对于将严格解决我有关在ECMAScript 2015及更高版本中对上述内置函数进行“超分类”的问题的第一个答案,我将奖励500奖励积分(其中Object.getPrototypeOf(Uint8Array.prototype).constructor
被介绍了。)
我不打算支持ECMAScript 5.1及更低版本,因为只能通过访问Object.setPrototypeOf()
来修改内置的继承链,__proto__
是 any 的一部分 em> ECMAScript规范,因此与实现有关。
P.S。我完全知道不鼓励使用此类做法的原因,这就是为什么我想确定该规范是否允许“超类”而不“破坏网络”(如TC39喜欢说的那样)。
答案 0 :(得分:3)
根据§9.3.3 CreateBuiltinFunction ( steps, internalSlotsList [ , realm [ , prototype ] ] )中概述的保证和§22.1.1 The Array Constructor中的步骤,对select from classDetails where standard like "Class [1-3]"
或Array(…)
的可能调用将不会调用Object构造函数或Array的构造函数可以在调用时解决超类,因此可以保证“超类”数组在ECMAScript 2018的任何符合实现中都能正常运行。
由于发现了第9.3.3节,我怀疑对于当前规范中的其余类也会得出相同的结论,尽管还需要进行大量研究来确定这是否正确,并保证可以返回ECMAScript。 2015。
这不是完整的答案,因此我不会接受。不论是否在我的问题有资格得到赏金之前就提供赏金,赏金仍将获得完整答案。
答案 1 :(得分: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(调度到AllocateTypedArray和IntegerIndexedObjectCreate算法)以及Map
和{{ 3}}构造函数(都分派给Set
和OrdinaryCreateFromConstructor算法)。而且afaik对于所有其他内置构造函数也一样,尽管我没有单独检查每个构造函数,但ES8的数量太多了。
我的理解是,由于
Array.prototype
本身是一个数组奇异对象,因此不需要遵从实现就可以在super()
构造函数中内部调用Array
来正确初始化实例为异国情调的数组
不,这与它无关。一个对象不会成为异质对象,因为它继承自异质对象。对象是奇异的,因为它是这样专门创建的。 Array.prototype
的值实际上可以是任何值,它与数组实例的创建无关-除此之外,在调用new Array
时它将用作原型(与{{1}相反) }。
关于new ArraySubclass
,请注意Object.setPrototypeOf(Array.prototype, …)
甚至不像Array.prototype
那样的ObjectCreate,所以可以的。