ECMAscript 6: watch changes to class properties - javascript

Let's say I have a class defined as follows:
class MyClass {
constructor (a) {
this.a = a;
}
_onPropertyChanged() {
// do something
}
}
Whenever the property "a" of an instance of MyClass changes, I want to trigger the _onPropertyChanged method on that instance.
What is the best (most performant) way to achieve this using ECMAscript 6?

There's no 'best' way, and the actual approach always depends on the final goal.
In its simplistic (and performant enough) form it is:
class MyClass {
constructor (a) {
this.a = a;
}
get a() {
return this._a;
}
set a(val) {
this._a = val;
this._onPropertyChanged('a', val);
}
_onPropertyChanged(propName, val) {
// do something
}
}

This is a simple example of what you can do for this situation and for a single property.
class MyClass {
constructor (a) {
this._a = a;
}
set a(value) {
let hasChanged = (this._a !== value);
this._a = value;
//Assumes value is primitive. Customize for objects
if(hasChanged) this._onPropertyChanged();
}
_onPropertyChanged() {
// do something
}
}

The easiest thing is to define a setter and a getter for the property like the previous comment said. However, it won't work if you already have a defined setter.
Another way is a non-standard Object.prototype.watch, which only works natively in Gecko. If you want to bring its support to most other browsers, you can use a small but powerful Andrea Giammarchi's library.

The best way is redefine filed/property, but in this case u have some pit falls, like 'find owner of this prop'. U can use this solution for totally-safe property redefining.
The main idea is redefine property if exists:
Object.defineProperty(target, name, {
get() { return get(ownPropertyDescriptor.get.call(target)); },
set(val) {
let _val = set.call(this, val);
if (_val != undefined)
ownPropertyDescriptor.set.call(target, _val);
},
configurable: true
});
Or create new property based on symbol type:
var indexer = Symbol(name);
target[indexer] = target[name];
Object.defineProperty(target, name, {
get() {
var val = target[indexer];
var _val = get(val);
return _val;
},
set(val) {
let _val = set.call(this, val);
arget[indexer] = set(_val);
},
configurable: true
});
Full method is part of complex solution for property-redefining.

Related

Javascript Convert Class Variable to Getter/Setter using Decorators

Any ideas how I can use a decorator to convert a class field into a getter/setter? Example:
class Foo:
#accessor bar = 0;
const foo = new Foo;
Should exhibit custom behavior on say, foo.bar = 1;
I have already tried something like
function accessor(target, name, descriptor) {
let val;
return {
set: function(newVal) {
val = newVal;
console.log("setter called");
},
get: function() { return val; }
};
}
but this loses the initial value of bar = 0.
The class requires to maintain private property where the value will be stored.
Since class fields aren't currently supported by decorators proposal and newer transform-decorators Babel plugin, older transform-decorators-legacy Babel plugin should be used instead.
As transform-decorators-legacy documentation suggests, in order to provide get/set accessors for a property, initializer method and writable property should be deleted from descriptor object. Since initializer function contains initial class field value, it should be retrieved and assigned to private property:
function accessor(classPrototype, prop, descriptor) {
if (descriptor.initializer)
classPrototype['_' + prop] = descriptor.initializer();
delete descriptor.writable;
delete descriptor.initializer;
descriptor.get = function () { return this['_' + prop] };
descriptor.set = function (val) { this['_' + prop] = val };
}
class Foo {
#accessor bar = 0;
}
const foo = new Foo ;
foo.bar = 1;
Due to the way how it works, initial value (0) will be assigned to class prototype and won't trigger set accessor, while next values (1) will be assigned to class instance and will trigger set accessor.
Since transform-decorators-legacy isn't spec-compliant, this won't work other decorator implementations, e.g. TypeScript and decorator proposal.
A direct spec-compliant ES6 counterpart for the code above is:
class Foo {
get bar() { return this._bar };
set bar(val) { this._bar = val };
}
Foo.prototype._bar = 0;

Good solution for multiple identical getter/setter functions in ES5/6 classes?

I'm looking for an elegant way to declare identical getter/setters for multiple properties in ES6 classes.
Rather than having a slew of:
set prop_1(value){
this.prop_1 = value;
console.log(`set prop_1 = ${value} and calling updateroom`);
db.updateRoom(this, 'prop_1');
}
...
set prop_n(value){
this.prop_n = value;
console.log(`set prop1 = ${value} and calling updateroom`);
db.updateRoom(this, 'prop_n');
}
I'd like to do something a little more maintainable like this in the class definition adjacent to the other getters and setters:
['prop_1', 'prop_2' ... 'prop_n'].forEach(prop => {
set [prop](value) {
this[prop] = value;
console.log(`set ${prop} = ${value} and calling updateroom`);
db.updateRoom(this, prop);
}
});
But of course can't do that inside the class definition as literals aren't one of the things syntactically allowed there.
Can't even add the setters to the class definition after declaration later via e.g.:
class Room {
// stuff
}
['initialised', 'type', 'owner'].forEach(prop => {
Object.defineProperty(Room, prop, {
set: function(value) {
this[prop] = value;
console.log(`set ${prop} = ${value} and calling updateroom`)
db.updateRoom(this, prop);
}
})
as there is no instance at that point.
So end up going down an arcane path of decorating the constructor, which just means anyone trying to figure out what the heck I was trying to achieve afterwards ends up with a half hour headache and way more complexity.
Am I missing something, has anyone figured out an elegant way of coding this efficiently without repetition of the getter-setters?
I'd like to do something a little more maintainable like this in the class definition adjacent to the other getters and setters:
['prop_1', 'prop_2' ... 'prop_n'].forEach(prop => {
set [prop](value) {
this[prop] = value;
console.log(`set ${prop} = ${value} and calling updateroom`);
db.updateRoom(this, prop);
}
});
and
Can't even add the setters to the class definition after declaration later via e.g.:...as there is no instance at that point.
Right, but you can use Object.defineProperty to do it, setting the properties on the object that will be the prototype of those instances (Room.prototype). After the class declaration:
class Room {
// ...
}
...you can add those setters to Room.prototype:
['prop_1', 'prop_2'/* ... 'prop_n'*/].forEach(prop => {
Object.defineProperty(Room.prototype, prop, {
set: function(value) {
// ...save the value somewhere (*NOT* `this[prop] = value;`,
// which will call the setter again, resulting in a stack
// overflow error...
}
});
});
Remember that class notation is mostly syntactic sugar on prototypical inheritance (but, you know, the good kind of sugar). You still have a Room.prototype object, and it's perfectly valid to add things to it outside the class declaration.
Live Example (in this example, I just store the values on a separate values property object):
class Room {
constructor() {
this.values = {};
}
}
['prop_1', 'prop_2', 'prop_n'].forEach(prop => {
Object.defineProperty(Room.prototype, prop, {
set: function(value) {
console.log(`set ${prop} = ${value}...`);
this.values[prop] = value;
},
get: function() {
return this.values[prop];
}
});
});
const r = new Room();
r.prop_1 = 42;
console.log("r.prop_1 = ", r.prop_1);
r.prop_2 = "Answer";
console.log("r.prop_2 = ", r.prop_2);

How to pass by reference or emulate it

I have two IFFE:
var Helper = (function () {
return {
number: null,
init: function (num) {
number = num;
}
}
})();
var Helper2 = (function () {
return {
options: {
number: [],
},
init: function(num){
this.options.number = num;
},
getData: function () {
return this.options.number;
}
}
})();
Helper2.init(Helper.number);
console.log(Helper2.getData());
Helper.init(5);
console.log(Helper2.getData());
What I want is
Helper2.init(Helper.number);
console.log(Helper2.getData()); // null
Helper.init(5);
console.log(Helper2.getData()); // 5
what I get is
Helper2.init(Helper.number);
console.log(Helper2.getData()); // null
Helper.init(5);
console.log(Helper2.getData()); // null
What techniques can be done to have it pass by reference, if it can?
JSBIN: https://jsbin.com/gomakubeka/1/edit?js,console
Edit: Before tons of people start incorporating different ways to have Helper2 depend on Helper, the actual implementation of Helper is unknown and could have 100's of ways they implement the number, so Helper2 needs the memory address.
Edit 2: I suppose the path I was hoping to get some start on was knowing that arrays/objects do get passed by reference, how can I wrap this primitive type in such a way that I can use by reference
Passing by reference in JavaScript can only happen to objects.
The only thing you can pass by value in JavaScript are primitive data types.
If on your first object you changed the "number:null" to be nested within an options object like it is in your second object then you can pass a reference of that object to the other object. The trick is if your needing pass by reference to use objects and not primitive data types. Instead nest the primitive data types inside objects and use the objects.
I altered you code a little bit but I think this works for what you were trying to achieve.
var Helper = function (num) {
return {
options: {
number: num
},
update: function (options) {
this.options = options;
}
}
};
var Helper2 = function (num) {
return {
options: {
number: num,
},
update: function(options){
this.options = options;
},
getData: function () {
return this.options.number;
}
}
};
var tempHelp = new Helper();
var tempHelp2 = new Helper2();
tempHelp2.update(tempHelp.options);
tempHelp.options.number = 5;
console.log(tempHelp2.getData());
First of all why doesn't it work:
helper is a self activating function that returns an object. When init is called upon it sets an number to the Helper object.
Then in Helper2 you pass an integer (Helper.number) to init setting the object to null. So you're not passing the reference to Helper.number. Only the value set to it.
You need to pass the whole object to it and read it out.
An example:
var Helper = (function () {
return {
number: null,
init: function (num) {
this.number = num; //add this
}
}
})();
var Helper2 = (function () {
return {
options: {
number: [],
},
init: function(obj){
this.options = obj; //save a reference to the helper obj.
},
getData: function () {
if (this.options.number)
{
return this.options.number;
}
}
}
})();
Helper2.init(Helper); //store the helper object
console.log(Helper2.getData());
Helper.init(5);
console.log(Helper2.getData());
I don't think you're going to be able to get exactly what you want. However, in one of your comments you said:
Unfortunately interfaces aren't something in javascript
That isn't exactly true. Yes, there's no strong typing and users of your code are free to disregard your suggestions entirely if you say that a function needs a specific type of object.
But, you can still create an interface of sorts that you want users to extend from in order to play nice with your own code. For example, you can tell users that they must extend from the Valuable class with provides a mechanism to access a value computed property which will be a Reference instance that can encapsulate a primitive (solving the problem of not being able to pass primitive by reference).
Since this uses computed properties, this also has the benefit of leveraging the .value notation. The thing is that the .value will be a Reference instead of the actual value.
// Intermediary class that can be passed around and hold primitives
class Reference {
constructor(val) {
this.val = val;
}
}
// Interface that dictates "value"
class Valuable {
constructor() {
this._value = new Reference();
}
get value() {
return this._value;
}
set value(v) {
this._value.val = v;
}
}
// "Concrete" class that implements the Valuable interface
class ValuableHelper extends Valuable {
constructor() {
super();
}
}
// Class that will deal with a ValuableHelper
class Helper {
constructor(n) {
this.options = {
number: n
}
}
getData() {
return this.options.number;
}
setData(n) {
this.options.number = n;
}
}
// Create our instances
const vh = new ValuableHelper(),
hh = new Helper(vh.value);
// Do our stuff
console.log(hh.getData().val);
vh.value = 5;
console.log(hh.getData().val);
hh.setData(vh.value);
vh.value = 5;

Can Object.defineProperty only modify the setter?

I want my object to have a field that when read returns the fields value, and when a val is written to the field, I want to modify the val before writing it. My current code is this:
function Cat(lives) {
var self = this;
var privateLives;
Object.defineProperty(self, 'publicLives', {
get: function() {return privateLives;},
set: function(val) {privateLives = 7 * val;}
});
}
Is there a way to do this without making a private variable? Ideally I would simply have the setter be this:
function(val) {self.publicLives = 7 * val;}
but that causes an overflow as the setter calls itself. Is there some way to make so it just doesn't loop the setter (so only assignment outside the setter's scope calls the setter and assignment in the setter just does normal assignment)? If that's possible, I wouldn't need to explicitly define a getter either as the setter writes to a public field.
No, this is not possible - a property can only be either a data property or an accessor property, not both. Of course you don't necessarily need to store the value in a private variable from your setter, you can use a different property or a property on a different object (like in #Oriol's proxy) as well. If you want to avoid private variables, "private" properties are the standard approach:
function Cat(lives) {
this.publicLives = lives;
}
Object.defineProperty(Cat.prototype, 'publicLives', {
get: function() {return this._privateLives;},
set: function(val) { this._privateLives = 7 * val;}
});
But you can also do some tricky things and hide the "private variable" by using a constant getter function that is repeatedly redefined:
Object.defineProperty(Cat.prototype, 'publicLives', {
set: function setter(val) {
val *= 7;
Object.defineProperty(this, 'publicLives', {
get: function() { return val; }
set: setter,
configurable: true
});
}
});
In ES6, an alternative would be using a Proxy object with a [[Set]] trap:
function Cat(lives) {
return new Proxy(this, {
set: function(target, prop, val) {
target[prop] = val;
if (prop === 'publicLives') target[prop] *= 7;
return true;
}
});
}

JS getters and setters inside a class?

I'd like to create a class in JS that uses native getters and setters. I know I can create getters/setters for objects, like so:
var obj = {
get value(){
return this._value;
},
set value(val){
this._value = val;
}
}
I also know that I can use this.__defineGetter__ inside a class/function, but MDN says that using __defineGetter__() etc is discauraged.
Is there any better way to add getters and setters to js class than:
function class(){
};
class.prototype = {
get value(){
//....
}
?
2019: Hooray for ES6!
class Person {
get name() {
return this._name + '!!!'
}
set name(newValue) {
this._name = newValue
}
constructor(name) {
this._name = name
}
}
const me = new Person('Zach')
console.log(me.name) // Zach!!!
me.name = 'Jacob'
console.log(me.name) // Jacob!!!
// Of course, _name is not actually private.
console.log(me._name) // Jacob
The cleanest way to define properties on a class is via Object.defineProperties. This allows you to define all of your properties in a single, easily readable block. Here's an example:
var MyClass = function() {
this._a = undefined;
this._b = undefined;
};
Object.defineProperties(MyClass.prototype, {
//Create a read-only property
a : {
get : function() {
return this._a;
}
},
//Create a simple read-write property
b : {
get : function() {
return this._b;
},
set : function(value) {
this._b = value;
}
}
});
There are a plethora of other options when defining properties, so be sure to check out the link I posted for more information. It's also important to keep in mind that even the most basic getter/setter property is only as fast as a method call in current browsers, so they can become a bottleneck in performance-intensive situation.
How about this implementation:
function makeObject(obj, name) {
// The property
var value;
// The setter
obj["get" + name] = function() {return value;};
// The getter
obj["set" + name] = function(v) {
value = v;
};
}
To experiment:
var obj = {};
makeObject(obj, "Name");
obj.setName("Lolo");
print(obj.getName());
Of course, you can test name for validity before storing it in value. The test can be supplied as an additional argument to the makeObject function.

Categories

Resources