Object Observation

I have been studying JavaScript frameworks recently, such as Angular and Meteor, and I was wondering how they would know when the property of an object changed so that they could update the DOM.

I was a little surprised that Angular used plain old JS objects instead of requiring you to call some kind of getter / setter so that it could connect and make the necessary updates. I understand that they just regularly check objects for changes.

But with the advent of getters and setters in JS 1.8.5, we can do better, right?

As a little proof of concept, I put together this script:

( Edit: updated code to add support for dependent properties / methods)

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 magazines:

Title changed from "Ryan Gosling" to "Ryan Reynolds"

The only problem I see is to define methods like fullName() for the person object, which will depend on two other properties. This requires a small additional add-in on the object so that developers can specify the dependency.

Other than this, are there any flaws in this approach?

Jsfiddle

+8
javascript design-patterns observable
source share
1 answer

the appearance of getters and setters in JS 1.8.5 - are there any drawbacks to this approach?

  • You do not record any changes in properties other than those observed. Of course, this is enough for simulated entity objects, and for something else we could use Proxies.
  • It is limited to browsers that support getters / seters and possibly even proxies. But hey, who cares about outdated browsers? :-) And in limited environments (Node.js) this doesn't work at all.
  • Accessor properties (with getter and setter) are much slower than real get / set methods. Of course, I do not expect them to be used in critical sections, and they can make the code very attractive. But you must keep this in the back of your mind. In addition, fancy code can lead to misconceptions - usually you expect assignment / access to resources to be short ( O(1) ), while with getters / setters much more can happen. You will need to remember this, and using real methods could help.

So, if we know what we are doing, yes, we can do better.

However, there is one huge point that we need to remember: synchronism / asynchrony (also see this great answer ). Angular dirty checking allows you to change a bunch of properties immediately before the event fires in the next turn of the event loop. This helps to avoid (propagate) semantically invalid states.

However, I see synchronous getters / setters as a chance. They allow us to declare dependencies between properties and determine the correct states. It will automatically ensure the correctness of the model, while we only need to change one property at a time (instead of changing firstName and fullName all the time firstName ). However, during the resolution of dependencies, which may be invalid, so we need to take care of this.

Therefore, non-dependency management listeners must be run asynchronously. Just setImmediate their loop.

+1
source share

All Articles