为啥在使用扩展语法复制对象后 getter/setter 不再工作?

Posted

技术标签:

【中文标题】为啥在使用扩展语法复制对象后 getter/setter 不再工作?【英文标题】:Why are getter/setter no longer working after copying an object with the spread syntax?为什么在使用扩展语法复制对象后 getter/setter 不再工作? 【发布时间】:2019-01-12 12:16:38 【问题描述】:

在下面的 sn-p 中,展开语法的工作方式我不太明白:

let obj = 
  set setName(name)
    obj.name = name
  ,
  get myName() 
    return obj.name
  

    
obj.setName = 'Jon Doe'

let spread_obj = ...obj
spread_obj.setName = 'Marion Luke'
console.log('spread_obj name', spread_obj.myName) // spread_obj name Jon Doe 

let create_obj = Object.create(obj)
create_obj.setName = 'Marion Luke'
console.log('create_obj name', create_obj.myName) // create_obj name Marion Luke

您能解释一下为什么在这种情况下重新分配名称不起作用吗?

【问题讨论】:

【参考方案1】:

选择的答案是错误的。

getter/setter 不是方法,它是特殊属性。 ...spread 和 object.assign 将不起作用,因为它们将 getter/setter 视为普通的可枚举属性,它们 复制它的“值”,因此 getter/setter 将失败。

如果要复制,可以参考undercore.js的extend方法:

这是我的分配代码:

const assign = (ob, ...o) => 
  o.forEach(obj=>if (typeof obj !== 'undefined')
  Object.defineProperties(ob, Object.getOwnPropertyDescriptors(obj));) 
  return ob;
;

【讨论】:

【参考方案2】:

除了@CertainPerformances 的解释之外,我还要补充一点,当使用 getter/setter 时,这种行为似乎更明智,因为 get 和 set 都使用一个名称。

例如,当您的对象看起来像这样时,一切都会好一些(至少在表面上):

let obj = 
  set name(name)        // setter and getter are accessed with the same property
    this._name = name
  ,
  get name() 
    return this._name
  


obj.name = 'Jon Doe'
let spread_obj = ...obj
spread_obj.name = 'Marion Luke'

// this seems more expected
console.log('spread_obj name', spread_obj.name)

// but this is still a little strange because
// Jon Doe is still there
console.log(spread_obj)

这似乎需要做很多工作,但由于对象传播只需要可枚举的属性,您可以将_name 设为不可枚举。然后一切似乎都变得更明智了:

let obj = 
  set name(name)
    this._name = name
  ,
  get name() 
    return this._name
  

Object.defineProperty(obj, '_name', 
  enumerable: false,
  writable: true,
  configurable: true
);

obj.name = 'Jon Doe'

let spread_obj = ...obj
spread_obj.name = 'Marion Luke'

// now spread_obj only has a name prop:
console.log(spread_obj)

// and obj too:
console.log(obj)

【讨论】:

【参考方案3】:

传播对象不会复制 getter 和 setter 方法 - 相反,传播操作仅获取属性(也就是说,它调用 getter,如果有),并将结果分配给结果对象(此处为 spread_obj),就像普通对象一样,没有任何 getter 或 setter。如果您将日志语句放在 setter 和 getter 中,您可以看到这一点。生成的spread_obj 具有undefinedsetName 属性,因为setName 只是一个setter 方法,它不会返回任何内容。

let obj = 
  set setName(name)
    console.log('setting ' + name);
    this.name = name
  ,
  get myName() 
    console.log('getting');
    return this.name
  


obj.setName = 'Jon Doe'
console.log('About to spread');
let spread_obj = ...obj
console.log('Done spreading. spread_obj contents is:');
console.log(spread_obj);
spread_obj.setName = 'Marion Luke' // No "setting" log statement

另外请注意,如果您想使用代码的第二部分,您可能应该将 setter 和 getter 方法更改为引用 this 而不是 obj,否则结果可能不直观;您的 getter 和 setter 方法区域始终引用 obj 上的 .name 属性,而不是标准调用上下文(即 spread_objcreate_obj)上的 .name 属性。当您尝试分配给spread_obj.setName 时,您实际上更改了原始 obj.name 属性:

let obj = 
  set setName(name)
    obj.name = name
  ,
  get myName() 
    return obj.name
  

obj.setName = 'Jon Doe'

let create_obj1 = Object.create(obj)
create_obj1.setName = 'Marion Luke1'

let create_obj2 = Object.create(obj)
create_obj2.setName = 'Marion Luke2'
// "Marion Luke2", but we're logging the property from the "first" object!
console.log('create_obj name', create_obj1.name)

通过引用this 而不是obj 进行修复:

let obj = 
  set setName(name)
    this.name = name
  ,
  get myName() 
    return this.name
  

obj.setName = 'Jon Doe'

let create_obj1 = Object.create(obj)
create_obj1.setName = 'Marion Luke1'

let create_obj2 = Object.create(obj)
create_obj2.setName = 'Marion Luke2'
console.log(create_obj1.name)

【讨论】:

我使用 obj 而不是这个,因为我认为 setter 和 getter 实际上是复制的。现在我意识到像Object.assign(SomeClassInstance.prototype, obj) 这样的东西也行不通。

以上是关于为啥在使用扩展语法复制对象后 getter/setter 不再工作?的主要内容,如果未能解决你的问题,请参考以下文章

为啥扩展元素不适合复制多维数组?

为啥我的扩展语法在节点 v7.0.0 中不起作用?

C++ - 为啥在删除后将对象设置为空? [复制]

为啥在调用它的析构函数后我可以访问这个堆栈分配的对象? [复制]

为啥使用扩展语法适用于基元和函数?

为啥我们不能在 Array.map() 中使用扩展运算符,还有啥可以替代展平数组? [复制]