当使用 Javascript 的 Reflect API 构建扩展另一个 class 的实例时,为什么索引数组是共享范围?

When using Javascript's Reflect API to build an instance of a class that extends another, why are indexed arrays a shared scope?

我已经使用 Typescript playground 构建了一个示例,但该行为与打字稿无关。在代码中更容易显示(link 包括已编译的 JS,对于那些希望忽略 TS 的人):

如果扩展 class,它使用索引数组作为 属性

class BaseClass {
  _meta: {[key: string]: string};
  constructor() {
    this._meta = {};
  }
}

class MyClass extends BaseClass {
  prop1?: string;

  constructor(init: Partial<MyClass>){
    super()
    Object.assign(this, init);
  }
}

然后从基础模型使用反射 API 创建其他实例,您可以获得不同的行为,具体取决于您如何构建后续模型。

如果您创建一个新对象并分配给 _meta,它会按照您期望的方式工作:

const baseInstance = new MyClass({
  prop1: 'base',
  _meta: {
    hello: 'world'
  }
})

const reflectInstance = Reflect.construct(MyClass, [baseInstance]);
reflectInstance._meta = { hello: 'reflection' }

alert("1 " + JSON.stringify(baseInstance)) // as expected
alert("2 " + JSON.stringify(reflectInstance)) // as expected

但是如果您使用数组表示法分配给容器,范围就会被污染,即它与源模型共享范围:

const contaminatedInstance = new MyClass({
  prop1: 'contaminated',
  _meta: {
    goodbye: 'world'
  }
})

const bogusInstance = Reflect.construct(MyClass, [contaminatedInstance]);
bogusInstance._meta['goodbye'] = 'contaminated';

alert("3 " + JSON.stringify(contaminatedInstance)) // _meta has unexpectedly changed
alert("4 " + JSON.stringify(bogusInstance)) // as a result of bogusInstance

有人知道这是为什么吗?我可以通过说 _meta 属性 有一个公共地址来模糊地证明事情的合理性,并且因为它被扩展了,所以没有 new 调用基本模型因此使其变得公共;但是当它出现在单元测试之外时,这是一个很难记住的案例;特别是在公关期间。

任何关于如何在仍然使用数组表示法的同时避免这种情况的建议都会很棒。

谢谢!

这与 Reflect 无关。您将获得相同的行为:

  new MyClass(new MyClass({ shared: "false", _meta: { shared: "true" } })

只是Object.assign shallow copies, so the _meta property of both instances will contain a reference to the same object..

一些伪内存结构使其更清晰:

  #1 { // MyClass
    [[Construct]]: Code,
    prototype: #2
  }

  #2 { // MyClass.prototype
    constructor: #1,
    // yet empty
  }

  #3 { shared: "true" } // the _meta object, created inside the object passed to the first instance constructor

  #4 { // the object passed to the first instance constructor
    shared: "false",
    _meta: #3,
  }

  #5 { // the first MyClass instance
    [[prototype]]: #2,
    shared: "false", // shallow copied from #4
    _meta: #3
  }

  #6 { // the second MyClass instance
    [[prototype]]: 2,
    shared: "false", // shallow copied from #5
    _meta: #3, // same reference as #4._meta
  }