ECMAScript Binding Manager
These days, most JavaScript libraries perform data binding. Data binding means updating the UI automatically as a result of a model change using the Observer-Observee principle. Here’s my take on the data binding standardization.
This blog post is a response to Rick Waldron’s interesting Object.observe blog post.
The Binding type
A binding object contains a Value Generator (a function that generate a result used as data source) and one or more Propagator (a function that use the data source to update the UI).
var ui = $("#id"); new Binding( v => obj.count, //v: old value v => (ui.textContent=v) //v: new value );
The Binding has a special behavior: when it evaluates its current value, it enters the JavaScript engine in a particular mode which keep tracks of the dependencies of its value generator by ‘collecting’ every property read and updated on any observable object during its call.
Later on, when any of those ‘dependency’ properties is updated, the Binding is automatically marked as out-of-sync by the browser.
Every now and then, the browser will re-execute the value generator of out-of-sync bindings, updating at the same time the dependencies of those bindings. In case the produced value changed, the propagators are being called with the new value in order to update the UI.
The Object.makeBindable() function
Not every object ought to be observable. For performance reasons, it’s a good idea to mark “observable objects” to give them a particular ‘observable’ behavior and keep “framework functions” and prototypes fast and efficient. It’s also a security feature which allow you to keep your objects and logic secret by default.
A bindable object is nothing more than an object whose calls to [[Put]], [[DefineOwnProperty]] and [[Delete]] are being logged, at least for the properties subject to a Binding dependency.
Also, during a Binding’s provided value computation, all its function calls including [[Get]], [[GetProperty]], [[GetOwnProperty]], [[HasProperty]], [[CanPut]] and [[DefaultValue]] are also being logged to track value generator’s dependencies.
A bindable object has an internal property, [[Observers]], which keeps tracks of every Binding’s depending on this object (and the observed properties). As soon as an observed property is updated, all the bindings of the [[Observers]] array which depends on it are marked out-of-sync.
define=Object.defineProperties; function Counter() { // private fields var privateThis= { count: 0 }; // public interface (functions) this.increment=function() { privateThis.count++; } // public interface (properties) define(this,{ count: { get() { return privateThis.count; }, set() { throw new ReadOnlyError(); } } }); Object.freeze(this); Object.makeBindable(privateThis); // making an object bindable doesn't // make it unsecure: no reference is // ever transmitted to JS code } var obj = new Counter(); // ... // create a new binding // ... obj.increment(); // updates ui.textContent the next 'turn'
This model is very decentralized and make GC work easier than in the case of global binding manager (which would require weak references to work).
Why this is a good model
This is a good model because it keeps much of the burden on the browser’s implementation side which allow for a ton of implementation optimizations. Also, it doesn’t force to make “bindable” objects public to the binding source.
Also, it’s trivial for developers to take advantage of it.
What do you think of it?