0

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

如果您扩展一个使用索引数组作为属性的类

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调用基本模型,因此使它变得通用;但是当它出现在单元测试之外时,这是一个很难记住的案例;尤其是在 PR 期间。

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

谢谢!

4

1 回答 1

1

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

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

只是Object.assign 浅拷贝,所以_meta两个实例的属性都包含对同一个对象的引用。.

一些伪内存结构使这一点更清楚:

  #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
  }
于 2019-08-12T20:01:34.643 回答