I've been looking into JavaScript frameworks such as Angular and Meteor lately, and I was wondering how they know when an object property has changed so that they could update the DOM.
I was a bit surprised that Angular used plain old JS objects rather than requiring you to call some kind of getter/setter so that it could hook in and do the necessary updates. My understanding is that they just poll the objects regularly for changes.
But with the advent of getters and setters in JS 1.8.5, we can do better than that, can't we?
As a little proof-of-concept, I put together this script:
(Edit: updated code to add dependent-property/method support)
function dependentProperty(callback, deps) {
callback.__dependencies__ = deps;
return callback;
}
var person = {
firstName: 'Ryan',
lastName: 'Gosling',
fullName: dependentProperty(function() {
return person.firstName + ' ' + person.lastName;
}, ['firstName','lastName'])
};
function observable(obj) {
if (!obj.__properties__) Object.defineProperty(obj, '__properties__', {
__proto__: null,
configurable: false,
enumerable: false,
value: {},
writable: false
});
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
if(!obj.__properties__[prop]) obj.__properties__[prop] = {
value: null,
dependents: {},
listeners: []
};
if(obj[prop].__dependencies__) {
for(var i=0; i<obj[prop].__dependencies__.length; ++i) {
obj.__properties__[obj[prop].__dependencies__[i]].dependents[prop] = true;
}
delete obj[prop].__dependencies__;
}
obj.__properties__[prop].value = obj[prop];
delete obj[prop];
(function (prop) {
Object.defineProperty(obj, prop, {
get: function () {
return obj.__properties__[prop].value;
},
set: function (newValue) {
var oldValue = obj.__properties__[prop].value;
if(oldValue !== newValue) {
var oldDepValues = {};
for(var dep in obj.__properties__[prop].dependents) {
if(obj.__properties__[prop].dependents.hasOwnProperty(dep)) {
oldDepValues[dep] = obj.__properties__[dep].value();
}
}
obj.__properties__[prop].value = newValue;
for(var i=0; i<obj.__properties__[prop].listeners.length; ++i) {
obj.__properties__[prop].listeners[i](oldValue, newValue);
}
for(dep in obj.__properties__[prop].dependents) {
if(obj.__properties__[prop].dependents.hasOwnProperty(dep)) {
var newDepValue = obj.__properties__[dep].value();
for(i=0; i<obj.__properties__[dep].listeners.length; ++i) {
obj.__properties__[dep].listeners[i](oldDepValues[dep], newDepValue);
}
}
}
}
}
});
})(prop);
}
}
return obj;
}
function listen(obj, prop, callback) {
if(!obj.__properties__) throw 'object is not observable';
obj.__properties__[prop].listeners.push(callback);
}
observable(person);
listen(person, 'fullName', function(oldValue, newValue) {
console.log('Name changed from "'+oldValue+'" to "'+newValue+'"');
});
person.lastName = 'Reynolds';
Which logs:
Name changed from "Ryan Gosling" to "Ryan Reynolds"
The only problem I see is with defining methods such as fullName()
on the person object which would depend on the other two properties. This requires a little extra markup on the object to allow developers to specify the dependency.
Other than that, are there any downsides to this approach?
JsFiddle
The process to create an Observable is fairly straightforward. First of all, we need to import Observable from rxjs. Then, we create an Observable by calling the new Observable constructor that takes one argument. In the following example, we create an Observable that emits a number every second to a subscriber.
There are two main methods to create Observables in RxJS. Subjects and Operators. We will take a look at both of these!
Observable are just that — things you wish to observe and take action on. Angular uses the Observer pattern which simply means — Observable objects are registered, and other objects observe (in Angular using the subscribe method) them and take action when the observable object is acted on in some way.
Observables are functions that throw values. Objects called observers subscribe to these values. Observables create a pub-sub system based on the observable design pattern. This makes observables popular with async programming in modern JavaScript frameworks like Angular and libraries like React.
Observable JavaScript represents a progressive way of handling events, async the activity, and multiple values in JavaScript. These observables are just the functions that throw values and Objects known as observers subscribe to such values that define the callback functions such as error(), next() and complete().
advent of getters and setters in JS 1.8.5 - are there any downsides to this approach?
O(1)
) operation, while with getters/setters there might be a lot of more happening. You will need to care not forgetting that, and the use of actual methods could help.So if we know what we are doing, yes, we can do better.
Still, there is one huge point we need to remember: the synchronity/asynchronity (also have a look at this excellent answer). Angular's dirty checking allows you to change a bunch of properties at once, before the event fires in the next event loop turn. This helps to avoid (the propagation of) semantically invalid states.
Yet I see the synchronous getters/setters as a chance as well. They do allow us to declare the dependencies between properties and define the valid states by this. It will automatically ensure the correctness of the model, while we only have to change one property at a time (instead of changing firstName
and fullName
all the time, firstName
is enough). Nevertheless, during dependency resolving that might not hold true so we need to care about it.
So, the listeners that are not related to the dependencies management should be fired asynchronous. Just setImmediate
their loop.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With