【发布时间】:2018-12-14 07:26:22
【问题描述】:
我正在寻找任何迹象表明“超类化”内置类型是否会起作用根据规范。也就是说,给定任何假设的 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 行为的子类构造函数必须包含对 Array 构造函数的超级调用,以初始化作为 Array 奇异对象的子类实例。
§22.1.3 Properties of the Array Prototype Object
Array 原型对象有一个 [[Prototype]] 内部槽,其值为内部对象 %ObjectPrototype%。
Array 原型对象被指定为 Array 外来对象,以确保与在 ECMAScript 2015 规范之前创建的 ECMAScript 代码兼容。
(强调)
我的理解是,不需要在 Array 构造函数中内部调用 super() 以将实例正确初始化为外来数组,也不需要 @987654335 @ 成为 Array 的直接超类(尽管我第一次引用 §22.1.3 似乎暗示了这一点)。
我的问题是,上面的第一个 sn-p 是否根据规范工作,或者它是否仅因为当前现有的实现允许它工作?即第一个HypotheticalArray的实现是否不符合?
对于全额赏金,我还想将这个问题应用于 String、Set、Map 和 TypedArray(我的意思是 Object.getPrototypeOf(Uint8Array.prototype).constructor)。
我将奖励 500 赏金积分,严格解决了我关于在 ECMAScript 2015 及更高版本中“超类化”上述内置函数(引入Object.setPrototypeOf() 的草案)的实践的问题)。
我不打算支持 ECMAScript 5.1 及以下版本,因为只有通过访问 __proto__ 才能修改内置函数的继承链,这 不是 any 的一部分ECMAScript 规范,因此依赖于实现。
附注我完全了解不鼓励这种做法的原因,这就是为什么我想确定规范是否允许“超类化”而不“破坏网络”,正如 TC39 喜欢说的那样。
【问题讨论】:
-
您是在问是否允许您更改数组(或其他内置类型)的原型链?
-
@Barmar 基本上是的,但仅限于我在我的稻草人提案中描述的“超类”类的限制。而且我想知道规范是否保证它在任何给定的实现中都不会产生意外的副作用。
-
你到底为什么要像这样修改现有全局变量的行为?即使它在技术上有效,你也会打破页面中任何现有代码的期望。
-
@loganfsmyth 这个问题的重点是确保根据规范,这种情况不会发生。除非现有代码期望
Object.getPrototypeOf(Array.prototype)是Object.prototype(这是没有充分的理由期望),否则没有人会合理地期望这会破坏任何现有的代码库。期望继承(我没有破坏)和期望直接超类(我在这里断言这是不合理的期望)之间存在差异。 -
好吧,够公平的。
标签: javascript arrays ecmascript-6 language-lawyer prototypal-inheritance