简单的检索和赋值 getter 和 setter 在 JavaScript 中有用吗?

Are simple retrieval and assignment getters and setters useful in JavaScript?

对 JavaScript 中的每个 属性 重复此模式是否有意义?

class Thing {
  get myProp() {
    return this._myProp;
  }
  set myProp(value) {
    this._myProp = value;
  }
}

我知道如果您在方法中做额外的工作,getters/setters 会很有用,但在这里重新创建基本功能似乎是不必要的重复。如果我实例化,我仍然可以直接操作支持 属性 (._myProp),所以我觉得我可以很容易地忽略这些并以更典型的临时方式执行我的分配和访问时尚

我想你可能会争辩说,以这种方式定义界面(使用下划线前缀 属性 名称)向用户发出信号,表明它并不是要操纵 属性,但这看起来像是可能有几十个这样的站不住脚的理由。

以您描述的方式使用访问器 属性(设置和检索 "background" 数据 属性)实际上在语义上与直接使用数据 属性 相同。有一些区别:访问器 属性 将存在于实例的原型上,而不是直接存在于实例上(尽管实例将具有 "background" 数据 属性),但这实际上不会除非您对 class 个实例进行高级内省,否则会影响任何事情。

唯一的优点是,如果您想在将来引入更复杂的访问器行为,可以轻松修改代码。如果您预见需要添加访问器行为,请使用此模式以节省您将来的时间。

属性 访问器可用于提供副作用或更改原始行为:

class Thing {
  get myProp() {
    console.log('myProp was read');
    return this._myProp;
  }
  set myProp(value) {
    if (!value)
      throw new Error('myProp cannot be falsy');
    this._myProp = value;
  }
}

myProp getter/setter 纯抽象没有意义:

class Thing {
  get myProp() {
    return this._myProp;
  }
  set myProp(value) {
    this._myProp = value;
  }
}

在编译语言中,人们这样做很常见。这是因为在那些语言中,分配给一个字段并调用一个 setter 对程序员来说可能是相同的,但它们编译为两个完全不同的操作。

例如,如果我想添加一个在 C# class 中设置字段的副作用,并且该字段是直接设置的而不是通过 setter?将其更改为 setter 会导致一些问题。我不必重写任何消耗代码,但我 必须重新编译所有代码。如果您的 public 版本已编译,这当然是一个大问题。

但是,

JavaScript 不受此类考虑,因此过早地将所有内容都变成 getter/setter 有点愚蠢。如果您看到有人这样做,很可能您正在与从另一种语言中学习约定并将其带入 JavaScript 的人打交道,而没有考虑很多 为什么。

If I instantiate, I can still manipulate the backing property (._myProp) directly,

如果您正在寻找私有状态,您仍然可以使用弱地图。

(function(scope) {
  "use strict";
  const prop = new WeakMap();

  scope.Foo = class {
    constructor() {
      prop.set(this, {});
      Object.seal(this);
    }
    get bar() {
      return prop.get(this)._bar;
    }
    set bar(value) {
      return prop.get(this)._bar = value;
    }
  }


}(this))

const f = new Foo;

f.bar = "bar";
f._bar = "_bar";
console.log(f.bar);
console.log(f._bar);

get 和 setter 在实现 MVC 时也很有用,您可以在 属性 更改时触发事件。

(function(scope) {
  "use strict";
  const prop = new WeakMap();

  scope.Foo = class {
    constructor() {
      prop.set(this, {});
      prop.get(this)._target = new EventTarget
      Object.seal(this);
    }
    get bar() {
      return prop.get(this)._bar;
    }
    set bar(value) {
      prop.get(this)._bar = value;
      prop.get(this)._target.dispatchEvent(new CustomEvent('change', {
        detail: value
      }));
    }
    addEventListener(event, listener) {
      prop.get(this)._target.addEventListener(event, listener)
    }
  }


}(this))

const f = new Foo;
f.addEventListener('change', function(event) {
  console.log("changed!", event.detail);
});
f.bar = "bar";