开发者问题收集

如何克隆 Javascript 类

2020-05-02
90

在单元测试期间,我想用一个名为 Bar 的类来模拟 Foo 类。 我必须更改 Foo 类中的属性以进行测试。

这是我尝试的。 它应该打印:

Foo

Bar

class Foo {
  id = 'foo'
  constructor() {
    console.log(this.id)
  }
}
const Bar = { ...Foo }
Bar.prototype.id = 'Bar'
new Foo()
new Bar()

但我得到的却是:

error: Uncaught TypeError: Cannot set property 'id' of undefined

我知道我可以通过创建一个新的 Bar extends Foo 类来实现这一点,但是还有其他可能的解决方案吗?

2个回答

Object Spread {...o 仅考虑 可枚举 属性。

来自 MDN (重点是我的):

Spread in object literals The Rest/Spread Properties for ECMAScript proposal (ES2018) added spread properties to object literals. It copies own enumerable properties from a provided object onto a new object.

类和函数的 prototype 属性不可枚举。因此,在 const Bar = {...Foo 中, Bar 将没有 prototype 属性。这就是为什么你会得到

error: Uncaught TypeError: Cannot set property 'id' of undefined

现在,为了让代码更接近你想要的,你可以这样写:

const Bar = {...Foo, prototype: Foo.prototype};

但是,现在 Bar 的行为与 Foo 不同,因为 它的 prototype 属性是可枚举的。

所以我们可以修改它并写成:

const Bar = {...Foo};
Object.defineProperty(Bar, 'prototype', {value: Foo.protoype, enumerable: false});
// `enumerable` defaults to `false` using this API, just being explicit for exposition.

但是,你的下一个语句 new B 仍然会失败,因为 B 仍然不是类对象或函数对象,因此不能与 new 运算符一起使用。

如果没有更多的上下文,很难说出你为什么真正想要这样做。

最直接的方法是编写以下

class Foo {
  id = 'Foo';
  constructor() {
    console.log(this.id);
  }
}
class Bar extends Foo {
  id = 'Bar';
}
new Foo() // logs 'Foo'
new Bar() // logs 'Bar'

如果您想避免使用继承,还有其他选择,但不清楚哪些是合适的,因为问题缺乏足够的背景来确定这一点。例如,如果您只想模拟通过 new Foo 创建 的对象,那么您可以使用辅助函数简单地模拟它们,该函数只需用测试值覆盖属性,如果您想模拟 Foo 本身,即类对象,那么替代方案会有所不同。

Aluan Haddad
2020-05-02

NodeJS 和 Babel 中的实例属性实际上并未设置为原型。它们设置在构造函数中。

检查此内容:

class Foo {
  id = 1
}

new Foo().hasOwnProperty('id') // => true

如果属性未在其自身内定义,则对象将仅在其原型上查找该属性,因此覆盖原型将不起作用。

如果您想避免使用 class 语法,您可以执行以下操作:

function Bar() {
  const o = new Foo()
  o.id = 'bar'
  return o
}

new Bar().id // => 'bar'
ichigolas
2020-05-02