复制和修改对象时,如何避免Javascript中的过时吸气剂?

问题描述

我试图允许使用transform方法对树结构进行转换,该方法映射树中的节点。树中的节点是使用get类型的getter定义的,当我创建新副本并转换节点的包含值时,这些节点不会被更新。

作为示例,请考虑以下简化版本:

function node() {
  return {
    foo: '123',get blah() { return this.foo; },transform(f) {
      const copy = {};
      Object.assign(copy,this);
      copy.foo = f(copy.foo);
      return copy;
    }
  };
}

const a = node();
const b = a.transform(value => '456');
a.blah
b.blah

即使foo已更新为'456',变换后的副本中的吸气剂仍会返回'123'。

返回对象的转换后的副本,但使用getter引用更新后的对象而不是源的正确方法是什么?

解决方法

Object.assign将调用getter;它们将不再存在:

function node() {
  return {
    foo: '123',get blah() { console.log('invoked');return this.foo; },transform(f) {
      const copy = {};
      Object.assign(copy,this);
      copy.foo = f(copy.foo);
      return copy;
    }
  };
}

const a = node();
const b = a.transform(value => '456');
console.log(Object.getOwnPropertyDescriptor(b,'blah'));

您可以使用getOwnPropertyDescriptors复制描述符,然后使用Object.defineProperties分配描述符:

function node() {
  return {
    foo: '123',get blah() { return this.foo; },transform(f) {
      const copy = {};
      const descriptors = Object.getOwnPropertyDescriptors(this);
      Object.defineProperties(copy,descriptors);
      copy.foo = f(copy.foo);
      return copy;
    }
  };
}

const a = node();
const b = a.transform(value => '456');
console.log(a.blah);
console.log(b.blah);

,

我将首先使用node函数本身来返回副本,即它可以创建具有任何foo值的新鲜的不可变对象:

function node(foo) {
  return {
    foo,transform(f) {
      return node(f(this.foo));
    }
  };
}

const a = node('123');
const b = a.transform(value => '456');
console.log(a.blah);
console.log(b.blah);
,

就我个人而言,我只会使用类似以下的构造函数:

function Node(){
  let wow = 'will not change length'; // I like that
  this.foo = '123';
  Object.defineProperties(this,{ //put all getters and setters in here
    blah:{ 
      get:()=>this.foo
    },length:{
      get:()=>{
        let l = 0;
        for(let i in this)l++;
        return l;
      }
    }
  });
}
const a = new Node,b = new Node;
b.foo = '456'; a.newProp = 'test';
console.log(a.blah); console.log(a.length); console.log(b.blah); console.log(b.length);

但是,您可能要使用class

class Node{
  constructor(){
    this.foo = '123';
  }
  get blah(){
    return this.foo;
  }
  get length(){
    let l = 0;
    for(let i in this)l++;
    return l;
  }
}
const a = new Node,b = new Node;
b.foo = '456'; a.newProp = 'test';
console.log(a.blah); console.log(a.length); console.log(b.blah); console.log(b.length);

我个人更喜欢构造函数,因为您可以在所有浏览器中拥有私有变量。