Reputation: 194
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()
.
Upvotes: 0
Views: 3590
Reputation: 2856
...a use case for this might be that
thing.x
andthing.y
should always round to an integer usingMath.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
Upvotes: 1
Reputation: 6768
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
's x/y properties. Then, the foo.position
setter would copy bar
's x/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.
Upvotes: 0
Reputation: 816442
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;
}
}
Upvotes: 3