【问题标题】:ES6: How to have a getter/setter for an object AND its propertiesES6:如何为对象及其属性设置 getter/setter
【发布时间】:2017-04-08 03:22:16
【问题描述】:

使用 JS getter 和 setter,我的目标是创建以下 API 案例:

// the user should be able to write this to update thing

thing = {
   x: 1,
   y: 2
}

// OR they should be able to write this

thing.x = 1
thing.y = 2

现在我正在使用这样的代码:

 get thing() {
   return {
     x: this._thing.x,
     y: this._thing.y
   };
 }

 set thing(value) {
   this._thing.x = value.x,
   this._thing.y = value.y
 }

这支持第一种情况,但不支持第二种情况。

这可以通过任何相当简单的方式完成吗?

编辑:我将输入一个示例,但一个用例可能是 thing.xthing.y 应该始终使用 Math.round() 舍入为整数。

【问题讨论】:

  • thing 返回一个新对象。
  • 是的,这是课程的一部分。还是你建议我把 _thing 变成一个类实例?因为,在我的项目中,它实际上已经是......
  • 为什么要有getter和setter呢?除非您没有提及特定原因,否则不使用它们会免费提供您想要的东西。
  • 为了清楚起见,我用代码缩短了问题。请相信我,我在这里需要 getter 和 setter。我会尝试发布一个更完整的代码示例。

标签: javascript ecmascript-6


【解决方案1】:

如果您必须使用 getter 和 setter,一个相对简单的解决方案是将 xy 定义为 getter 和 setter。

get thing() {
  var self = this;
  return {
    get x() {
      return self._thing.x;
    }
    set x(value) {
      self._thing.x = value;
    }
    // same for y
  };
}

但您必须注意,每次访问thing 以进行读取,都会创建一个新对象。尽管您可以通过缓存该对象并重用它来避免这种情况。

事实上,我可能根本不会有_thing。我只需存储_x_y 并按需生成一个对象:

class Foo {
  get thing() {
    if (this._thing) {
      return this._thing;
    }

    var self = this;
    return this._thing = {
      get x() {
        return self._x;
      }

      set x(value) {
        self._x = value;
      }
    };
  }

  set thing(value) {
    this._x = value.x;
    this._y = value.y;
  }
}

【讨论】:

  • 这看起来很有希望!我会尝试一下...缓存对象是否意味着本质上将其创建为先前的变量?我是否正在考虑需要thing_thing__thing?我可以看到这会变得很难看,但在这种特定情况下可能有用。
  • 查看我添加的更完整的示例。
  • 这很有用。这有点棘手,因为对我来说,_thing 已经是另一个类实例......但我认为你描述的额外抽象层可以解决这个问题。返回带有 getter 的 getter 和 setter 看起来是正确的。一个修复——我想你想在第 10 行使用self._x,对吗?
  • 谢谢,太好了。一旦我对此进行测试,我将标记为已回答。
  • 这正是我们想要拥有 XY 类而不是返回对象字面量的情况。它看起来会更干净。还有it will be faster.
【解决方案2】:

...一个用例可能是thing.xthing.y 应该始终使用Math.round() 舍入为整数。

考虑使用Proxy

class Parent {
  constructor() {
    this.thing = {x: 15.6, y: 2.1};
  }

  set thing(value) {
    this._thing = new Proxy(value, {
      get: function(target, property, receiver) {
        // Catch all get access to properties on the object
        if (['x', 'y'].includes(property)) {
          // for x and y, return the real value, rounded
          return Math.round(target[property]);
        }
      }
    });
  }
  
  get thing() {
    return this._thing;
  }
}

var parent = new Parent();

console.log(parent.thing.x); // 16

parent.thing.x = 13.2;

console.log(parent.thing.x); // 13

parent.thing = {x: 10.1, y: 5.4};

console.log(parent.thing.y); // 5

// This works for the Jacque Goupil's conundrum
var anotherRef = parent.thing;
anotherRef.x = 5.8;
console.log(parent.thing.x); // 6

// In fact, if you wanted to dance with the devil...
var plainObj = {x: 10.1, y: 5.4};
parent.thing = plainObj;
plainObj.x = 7.2;
console.log(parent.thing.x); // 7
parent.thing.x = 22.2;
console.log(plainObj.x); // 22.2

代理允许您捕获对属性的获取或设置操作。

警告: IE 目前不支持代理。如果我有Google's Proxy polyfill 的 CDN,我会将其添加到 sn-p,但我没有。另外,如果你使用Babel,还有babel-plugin-proxy

【讨论】:

  • 我也会试试这个,但是当我运行代码 sn-p 时它会产生一个错误(“脚本错误”)。你知道为什么吗?
  • 当你在 SO 上运行它时?我看到 16 和 13 作为输出
  • 我在 Chrome 和 FF 中运行它,它运行良好,但在 IE 中它看起来需要一个 polyfill,等一下,我看看能不能找到。
  • 答案已更新以涵盖其他答案中提出的要点
【解决方案3】:

我认为你不应该使用这种模式,而且我认为你很难让它发挥作用。考虑以下情况:

// calls the position setter on foo
foo.position = {x: 10, y: 20};

// calls the position getter on foo, obtaining a simple {x:10,y:20} object
var pos = foo.position;

// calls the position getter on foo and then the x getter on position
var xx = foo.position.x;

到目前为止,一切都说得通。然后我们就遇到了这种情况:

// calls the position getter on foo and then the x setter on position
foo.position.x = 7;

由于我们返回了一个带有位置 getter 的简单地图,因此 position.x 只是分配给返回的副本,而不修改 foo 的实际位置。解决此问题的一种方法是让位置 getter 返回一个更智能的对象,该对象具有对 foo 实例和正确 getter/setter 的引用。这将允许这样做:

foo.position = bar.position;
bar.x += 10;

bar.position getter 将返回一个对象,该对象仅充当bar 的 x/y 属性的视图。然后,foo.position 设置器会将bar 的 x/y 属性复制到其私有存储中。说得通。 bar.x += 10 仅向 bar 的位置投放广告。

但是,下面的情况会很混乱:

var temp = foo.position;
foo.position = bar.position;
bar.position = temp;

这造成了 temp 是 foo.position 的备份副本的错觉,但事实并非如此。这是一个视图。一旦 foo 的位置发生变化,我们就会永久丢失数据。没有简单的方法可以复制位置。

您可以尝试让您的 position 对象实际存储数据的副本以及对原始数据的引用,这样您就可以同时进行设置和获取...这是一个无穷无尽的问题。

但在这一点上,你的糖语法使一切变得更难维护,效率也大大降低。

因此,我实际上不是做所有这些,而是​​让 x 和 y getter/setter 位于 foo/bar 对象本身上。我还会让 foo/bar 对象中的任何代码将位置视为不可变的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-07-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-24
    • 1970-01-01
    • 2012-01-31
    • 2019-06-30
    相关资源
    最近更新 更多