Introduction to Object.observe
Two-way data binding is now one of the crucial features of client-side applications. Without data binding, a developer has to deal with a lot of logic to manually bind data to the view, whenever there is a change in the model. JavaScript libraries like Knockout, AngularJS, and Ember have support for two-way binding but these libraries use different techniques to to detect changes.
Knockout and Ember use observables. Observables are functions wrapped around the properties of the model objects. These functions are invoked whenever there is a change of the value of the corresponding object or property. Although this approach works well, and detects and notifies all the changes, it takes away the freedom of working with plain JavaScript objects as now we have to deal with functions.
Angular uses dirty checking to detect changes. This approach doesn’t pollute the model object. It registers watchers for every object added to the model. All of these watchers are executed whenever Angular’s digest cycle kicks in and if there are any changes to the data. Those changes are processed by the corresponding watchers. The model still remains a plain object, as no wrappers are created around it. But, this technique causes performance degradation as the number of watchers grows.
What is Object.observe
?
Object.observe
, a.k.a. O.o
, is a feature to be added to JavaScript as part of ECMAScript 7 to support object change detection natively in the browser. Although ES7 is not completed yet, this feature is already supported in Blink-based browsers (Chrome and Opera).
Because Object.observe
will be supported by the browsers natively and it works directly on the object without creating any wrappers around it, the API is both easy to use and a win for performance. If Object.observe
is supported by a browser, you can implement two-way binding without the need of an external library. It doesn’t mean that all of the existing two-way binding libraries will be of no use once O.o
is implemented. We still need them to update UIs efficiently after detecting the changes using O.o
. Besides, libraries would internally polyfill the logic of change detection if not all targeted browsers support O.o
.
Observing Properties of an Object
Now that you have an idea of what O.o
is good for, let’s see it in action.
The observe()
method is an asynchronous static method defined on Object
. It can be used to look for changes of an object and it accepts three parameters:
- an object to be observed
- a callback function to be called when a change is detected
- an optional array containing types of changes to be watched for
Let’s see an example of using the method. Consider the following snippet:
var person = {
name: 'Ravi',
country: 'India',
gender: 'Male'
};
function observeCallback(changes){
console.log(changes);
};
Object.observe(person, observeCallback);
person.name = 'Rama'; // Updating value
person.occupation = 'writer'; // Adding a new property
delete person.gender; // Deleting a property
In this code we created an object literal with some data. We also defined a function named observeCallback()
that we’ll use to log the changes of the object. Then, we start observing for changes by using O.o
. Finally, we performed some changes on the object.
If you see the output on the console, you’ll see that all of the three changes are detected and logged. The following screenshot shows the result produced by the snippet:
O.o
runs asynchronously and it groups all of the changes happened and passes them to the callback when it’s called. So, here we received three entries for the three changes applied on the object. As you see, each entry consists of name of the property changed, the old value, the type of the change, and the object itself with the new values.
A live demo of the previous code is reported below (remember to open the console to see the result):
See the Pen emKveB by SitePoint (@SitePoint) on CodePen.
In our code we didn’t specify the types of changes to look for, so it observes additions, updates and deletes. This can be controlled using the third parameter of the observe
method as follows:
Object.observe(person, observeCallback, ['add', 'update']);
Registering Notifications
The observe()
method is capable of detecting changes made on direct properties added to an object. It cannot detect changes on properties created using getters and setters. Because the behavior of these properties is controlled by the author, changes detection also have to be owned by the author. To address this issue, we need to use a notifier (available through Object.getNotifier()
) to notify the changes made on the property.
Consider the following snippet:
function TodoType() {
this.item = '';
this.maxTime = '';
var blocked = false;
Object.defineProperty(this, 'blocked', {
get:function(){
return blocked;
},
set: function(value){
Object.getNotifier(this).notify({
type: 'update',
name: 'blocked',
oldValue: blocked
});
blocked = value;
}
});
}
var todo = new TodoType();
todo.item = 'Get milk';
todo.maxTime = '1PM';
console.log(todo.blocked);
Object.observe(todo, function(changes){
console.log(changes);
}, ['add', 'update']);
todo.item = 'Go to office';
todo.blocked = true;
TodoType
is a constructor function with two properties. In addition to them, blocked
is added using Object.defineProperty
. In our example the setter defined for this property is a simple one. In a typical business application, it may perform some validations and it may not set value in case the validation fails. However, I wanted to keep things simple.
As a last note, you can see that in our example the notification is sent only when there is an update.
The change made to the property blocked
produces the following result in the Chrome developer tools:
A live demo of this example is reported below (remember to open the console to see the result):
See the Pen NPzgOO by SitePoint (@SitePoint) on CodePen.
Observing Multiple Changes
Sometimes, we may have a computation to run after two or more properties are modified in some way. Though we can notify both of these changes individually using a notifier, it would be better to send a single notification with a custom type name to indicate that both of the values are modified. This can be done using the notifier.performChange()
method. This method accepts three arguments:
- Name of the custom type
- Callback function performing the changes. Value returned from this function is used in the change object
- Object on which the changes are applied
Let’s add a new property named done
to the class TodoType
defined above. The value of this property specifies if the todo item is completed or not. When the value of done
is set to true
, we need to set the value of the property blocked
to true
as well.
The following snippet defines this property:
var done = false;
Object.defineProperty(this, 'done', {
get: function(){
return done;
},
set: function(value){
if(value){
var notifier = Object.getNotifier(this);
if(blocked && value) {
notifier.performChange('doneAndUnblocked', function(){
done = value;
blocked = false;
return { oldDone: false, oldBlocked: true };
}, this);
}
else{
notifier.notify({
type: 'update',
name: 'done',
oldValue: done
});
done = value;
}
}
}
});
Once the logic inside the callback of performChange
is executed, the change will be notified with the custom change type passed into it. This type is not observed by Object.observe
by default; we need to explicitly ask O.o
to observe changes of the custom type. The following snippet shows a modified O.o
on the todo
object to observe change of the custom type along with add and update types:
Object.observe(todo, function(changes){
console.log(changes);
}, ['add', 'update', 'doneAndUnblocked']);
todo.blocked = true;
todo.done = true;
The above snippet sets the value of blocked to true
before setting done
to true
. So, it sends a notification with the custom change type. The following screenshot shows details of the change object returned by the custom type:
A live demo of this example is reported below (remember to open the console to see the result):
See the Pen yyEXGd by SitePoint (@SitePoint) on CodePen.
Observing Arrays
Observing Arrays is similar to observing objects. The only difference is that the observer function has to be registered using Array.observe
instead of Object.observe
. The following snippet demonstrates this:
var array = ['morning', 'Afternoon', 'Evening'];
var arrayObserver = function(changes){
console.log(changes);
};
Array.observe(array, arrayObserver);
array[0] = 'Morning';
array.push('Night');
array.splice(1, 1);
A live demo of this example is reported below (remember to open the console to see the result):
See the Pen GgGEzQ by SitePoint (@SitePoint) on CodePen.
Removing Registered Observers
A registered observer on an object or array can be removed using Object.unobserve()
or, Array.unobserve()
respectively. This method accepts two parameters, the object or array and the callback to be removed. So, to use this method we need to have a reference of the callback.
Object.unobserve(person, observeCallback);
Conclusion
Once O.o
is fully supported by all browsers, change detection will be standardized across all client side libraries. Aurelia already started using it, the change detection library of Angular 2, watchtower.js, uses O.o
internally, and Ember is also going to use it for change detection in future. Angular 2 and Aurelia have pollyfills implemented to fallback when O.o
is not natively available.
The future around client side two-way binding will be brighter with this great addition to the browsers. Let’s look forward for other browsers to catch up sooner!