这个答案故意详细说明“我认为你不能”,以帮助其他试图做同样事情的人理解我为什么得出这个结论,至少
不使用eval()
可能甚至连这个都不行。我对JavaScript原型类实现有相当深入的了解,但有许多人比我更深入了解。
我的要求
我在这里提出了这个问题,并最终选择了一个类工厂。我的要求是克隆的类与使用类工厂获得的类完全相同。具体来说,我想要一个函数,使得这个函数:
class Parent {}
class Child extends Parent {}
const Sibling = cloneClass(Child)
结果与此完全相同:
class Parent {}
function classFactory() {
return class Child extends Parent {}
}
const Child = classFactory()
const Sibling = classFactory()
我的目的中有以下是不能接受的:
Things that are thus dealbreakers for my purposes:
Sibling
是 Child
的实例或者原型继承自 Child
(它的第一个原型应该是 Parent
)
Sibling
与 Child
共享原型或函数定义
对于我的需求,如果 Sibling.name == Child.name == 'Child'
,那么这将非常有用,这在类工厂设置中是成立的。
严格来说,还有更多要求才能完全相同(例如,Sibling
的方法不能从 Child
的方法原型继承下来),但我认为这是无关紧要的,很快就会明白为什么。
难点
根据我的理解,使这件事情变得不可能的原因其实很简单:它需要克隆函数,而你无法做到这一点。这里有一些关于此的
问题和
解决方案,它们在某些情况下可以工作,但并非真正意义上的克隆,并且不能满足我的要求。值得一提的是,函数也被列为 lodash 的“
无法克隆的值”之一。
这与类方法相关,但更为关键的是,它与类实际上是函数有关。从字面上讲,一个类就是它的构造函数。因此,即使你能够处理从
Child
方法继承的类方法,我认为你也无法避免这样一个事实,即
Sibling
类本身 - 它是一个函数 - 也必须从
Child
继承。
英译中:
编辑:如果Child
没有构造函数,那么您可能可以绕过此要求。
其他想法/注意事项
我很高兴被证明是错误的,或者我的理解被纠正了,但我认为这是中心障碍:类是函数,你不能克隆一个函数。
我没有追求的一条路是使用Function()
构造函数,几乎但不完全地评估自己成为一个真正的“克隆”函数。我不确定这是否可能,而且我对这样做的影响不够了解,因此不敢尝试。
要求演示
如果您想尝试一下,我制作了一个片段,其中包含一些测试,这些测试断言我的要求-如果您可以使用克隆函数使我的要求通过,请告诉我!
编辑:@Bergi提供了一个解决方案,我已将其包含在下面的代码片段中。它似乎能够完成工作!我相信它避免了通过克隆构造函数来实现的问题,因为在我的情况下,子类没有自己的构造函数。因此,一个空函数(其他所有内容都附加在其中)确实等同于克隆。它还带有关于
使用setPrototype的所有标准免责声明。
'use strict'
let hadWarning = false
const getProto = Object.getPrototypeOf
class Parent {}
function classFactory () {
return class Child extends Parent {}
}
function factoryTest () {
const Child = classFactory()
const Sibling = classFactory()
runTest(Child, Sibling, 'classFactory')
}
function cloneClass (Target, Source) {
return Object.defineProperties(
Object.setPrototypeOf(
Target,
Object.getPrototypeOf(Source)
),
{
...Object.getOwnPropertyDescriptors(Source),
prototype: {
value: Object.create(
Object.getPrototypeOf(Source.prototype),
Object.getOwnPropertyDescriptors(Source.prototype)
)
}
}
)
}
function berghiTest () {
class Child extends Parent {}
const Sibling = cloneClass(function Sibling () {}, Child)
runTest(Child, Sibling, 'Bergi\'s clone')
}
factoryTest()
berghiTest()
function fail (message, warn) {
if (warn) {
hadWarning = true
console.warn(`Warning: ${message}`)
} else {
const stack = new Error().stack.split('\n')
throw new Error(`${message} ${stack[3].trim()}`)
}
}
function assertEqual (expected, actual, warn) {
if (expected !== actual) {
fail(`Expected ${actual} to equal ${expected}`, warn)
}
}
function assertNotEqual (expected, actual, warn) {
if (expected === actual) {
fail(`Expected ${actual} to not equal ${expected}`, warn)
}
}
function runTest (Child, Sibling, testName) {
Child.classTag = 'Child'
Sibling.classTag = 'Sibling'
hadWarning = false
assertEqual(Child.name, 'Child')
assertEqual(Sibling.name, Child.name, true)
assertEqual(Child.classTag, 'Child')
assertEqual(Sibling.classTag, 'Sibling')
assertEqual(getProto(Child).name, 'Parent')
assertEqual(getProto(Sibling).name, 'Parent')
assertEqual(getProto(Child), Parent)
assertEqual(getProto(Sibling), Parent)
assertNotEqual(Child.prototype, Sibling.prototype)
assertEqual(getProto(Child.prototype), Parent.prototype)
assertEqual(getProto(Sibling.prototype), Parent.prototype)
const child = new Child()
const sibling = new Sibling()
assertEqual(sibling instanceof Child, false)
assertEqual(child instanceof Parent, true)
assertEqual(sibling instanceof Parent, true)
if (hadWarning) {
console.log(`${testName} passed (with warnings)`)
} else {
console.log(`${testName} passed!`)
}
}
class
。应该使用对象字面量代替。 - Bergi