javascript - ECMAScript 规范是否允许 Array 成为“超类”?
问题描述
我正在寻找任何迹象表明“超类化”内置类型是否会根据规范工作。也就是说,给定 ECMAScript 的任何假设一致实现,“超类化”内置函数是否会通过影响类构造函数的创建算法来破坏运行时?
“Superclassable”,我正在创造的一个术语,指的是一个类,其通过构造它返回的对象,或者如果适用,将其作为函数调用,将使用相同的内部槽(除了 [[Prototype]])创建,不管它的直接超类是什么,只要类构造函数的初始[[Prototype]]和类原型在重新分配后仍然在各自的继承链中。因此,为了成为“超类”,类在创建期间不得调用。 super()
当“超类化” anArray
时,我希望它看起来像这样:
// 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 行为的子类构造函数必须包含对 Array 构造函数的超级调用,以初始化作为 Array 奇异对象的子类实例。
Array 原型对象有一个 [[Prototype]] 内部槽,其值为内部对象 %ObjectPrototype%。
Array 原型对象被指定为 Array 外来对象,以确保与在 ECMAScript 2015 规范之前创建的 ECMAScript 代码兼容。
(重点补充)
我的理解是,为了将实例正确初始化为外来数组,不需要在构造函数内部调用符合要求的实现,也不需要成为的直接超类(尽管我第一次引用 §22.1.3 似乎暗示那一点)。super()
Array
Object
Array
我的问题是,上面的第一个片段是否根据规范工作,还是仅因为当前现有的实现允许它工作?即是第一个HypotheticalArray
不符合项的执行?
对于全额赏金,我还想将这个问题应用于String
、Set
、Map
和TypedArray
(我的意思是Object.getPrototypeOf(Uint8Array.prototype).constructor
)。
我将奖励 500 赏金积分,第一个答案严格解决了我关于在 ECMAScript 2015 及更高版本(Object.setPrototypeOf()
引入的草案)中“超类化”上述内置函数的实践的问题。
我不打算支持 ECMAScript 5.1 及以下版本,因为修改内置的继承链只能通过访问来实现__proto__
,这不是任何ECMAScript 规范的一部分,因此依赖于实现。
PS 我完全清楚不鼓励这种做法的原因,这就是为什么我想确定规范是否允许“超类化”而不“破坏网络”,正如 TC39 喜欢说的那样。
解决方案
基于§9.3.3 CreateBuiltinFunction ( steps , internalSlotsList [, realm [, prototype ]] )和§22.1.1 The Array Constructor中概述的保证,不可能调用Array(…)
或new Array(…)
将调用 Object 构造函数或构造函数Array 在调用时解析的超类,因此“超类化”Array 保证在任何符合 ECMAScript 2018 的实现中都能正常运行。
由于 §9.3.3 的发现,我怀疑当前规范中的其余类也会得出相同的结论,尽管需要更多的研究来确定这是否准确,并保证回到 ECMAScript 2015。
这不是一个完整的答案,因此我不会接受它。无论是否在我的问题有资格获得赏金之前提供赏金,完整答案仍将获得赏金奖励。
推荐阅读
- flutter - 带有过滤器搜索页面的颤振在返回页面时不断从查询中添加相同的列表视图
- html - R:将多个 html 小部件保存在一起
- php - 在laravel中通过id计算值
- sockets - UDP 套接字客户端无法与 Minikube 上的 UDP 套接字服务器通信
- selenium - Firefox 84.0.1(64 位)的 Web 驱动程序
- c# - 在 C# 中启用特定密码套件
- python - 通过循环有效地将数据添加到 h5py 数据集
- java - 由于“无法解析 xml 报告”,TeamCity 构建失败:“在 CDATA 部分中发现了无效的 XML 字符(Unicode:0xffff)。”
- mysql - 插入意图锁是一种特殊类型的间隙锁,可以共存吗?
- python - 包含的 URLconf 'myapp.urls' 中似乎没有任何模式