简体   繁体   中英

ES6: How to have a getter/setter for an object AND its properties

Using JS getters and setters, I am aiming to create the following API case:

// 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

Right now I am using code like this:

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

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

This supports the first case, but not the second case.

Can this be done in any reasonably simple way?

EDIT: I will type up an example, but a use case for this might be that thing.x and thing.y should always round to an integer using Math.round() .

If you have to use getters and setters, a relatively simple solution would be define x and y as getters and setters as well.

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

But you have to be aware that every time thing is accessed for reading , a new object will be created. Though you could avoid this by caching that object and reuse it.

In fact, I probably wouldn't have _thing at all. I'd just store _x and _y and generate an object once on demand:

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;
  }
}

...a use case for this might be that thing.x and thing.y should always round to an integer using Math.round() .

Consider using a 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 

The proxy allows you to catch a get or set operation on a property.

Caveat: IE doesn't natively support proxies for the moment. If I had a CDN for Google's Proxy polyfill I would add it to the snippet, but I don't. Also if you use Babel , there's babel-plugin-proxy

I don't think you should use this pattern, and I think you'll have trouble making it work. Consider the following cases:

// 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;

So far, everything makes sense. Then we get to this situation:

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

Since we returned a simple map with the position getter, position.x simply assigns to the returned copy and doesn't modify foo's actual position. One way to fix this would be to have the position getter return a smarter object that has a reference to the foo instance and proper getter/setters. This would allow doing:

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

The bar.position getter would return an object that merely acts as a view for bar 'sx/y properties. Then, the foo.position setter would copy bar 'sx/y properties into its private storage. Makes sense. bar.x += 10 ads only to bar 's position.

However, the following situation would be very confusing:

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

This creates the illusion that temp is a backup copy of foo.position but it isn't. It's a view. As soon as foo's position changes, we lose the data for good. There's no easy way to copy the position.

You could try making your position objects actually store both a copy of the data as well as a reference to the original, so that you can do both set and get... It's an endless problem.

But at this point, your sugar syntax is making everything much harder to maintain as well as much less efficient.

So instead of doing all this, I'd actually make is so that the x and y getter/setters are on the foo/bar objects themselves. I'd also make any code inside the foo/bar object treat positions as if they were immutable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM