Introduction
Revolution is coming. There is a new addition to JavaScript, which will change everything that you have ever known about date-buying. In addition, your MVC libraries' approach to observing editing and updating models will also change. You are ready?
Good good. We will not pull. I'm glad to introduce Object.observe (), which appeared in the beta version of Chrome 36. [CROWD ELIMINATES]
Object.observe () is part of the following ECMAScript standard. It allows you to asynchronously track changes to JavaScript objects ... without using any third-party libraries, it allows the observer to track changes in the state of an object over time.
')
Every time an object changes, we receive an alert:

Using Object.observe () (I like to call it Oo () or Oooooooo), you can implement two-way data-binding without having to use the framework.
This does not mean that you should not use it. For large projects with complex business logic, frameworks are necessary, and I’m not going to dissuade you from using them. They are aimed at simplifying work for new developers, require less code for support, and introduce specific templates for working on common tasks. When you do not need this functionality, you can use more lightweight solutions, such as polymer (which, by the way, already uses Oo ()).
Even if you use frameworks or MV * libraries in full, Oo () will allow them to get a good performance boost, which is achieved through a quick, simplified implementation, and at the same time continues to use the same API. For example, last year, the Angular development team conducted a comparative analysis and found that dirty-checking takes about 40ms, while O.O () takes about 1-2ms (it turns out that it is 20-40 times faster).
Date-buying without having to use tons of complex code! But this also means that you no longer have to poll the model to receive changes!
If you already understand what OO () is doing, you can immediately flip to the description of the new functionality, or you can read what problems this approach solves.
What are we going to watch?
When we talk about observing data, we usually mean tracking several types of changes:
- Modifying Native JavaScript Objects
- Adding, changing or deleting properties
- When data is deleted or added to an array
- Changes in object prototype
About the importance of data-buying
Date-buying starts becoming an important part of your application when you begin to affect the interaction between the model and the view. HTML is a great declarative mechanism, but it is completely static. Ideally, you just want to associate your data with the DOM and keep it up-to-date. The solution with Oo () allows you to save a lot of time, due to the lack of need to write large chunks of repeating code that will simply send new data to the DOM, and vice versa.
Date-buying is really useful when you create a comprehensive user interface, where you need to build a large number of links between the various properties of your models and the UI elements that reflect them. This is one of the most common tasks when creating SPA (Single Page Application) applications.
Initially, we had no mechanism for observing the data, and we shifted responsibility for this to various JavaScript frameworks (or wrote small libraries), relying on various slow hacks that the world uses to this day.
What is the world like today?
Dirty-checking
Where have you seen date-buying until now? Well, if you use modern MV * libraries to build your web applications (Angular, Knockout), then you may have already used model data binding to your DOM. To refresh it in memory, here is an example of the “Phonebook” application, where we add the number of each phone from the array of numbers to the list item, and thus maintain constant synchronization between them:
<html ng-app> <head> ... <script src="angular.js"></script> <script src="controller.js"></script> </head> <body ng-controller="PhoneListCtrl"> <ul> <li ng-repeat="phone in phones"> {{phone.name}} <p>{{phone.snippet}}</p> </li> </ul> </body> </html>
and JavaScript for the controller:
var phonecatApp = angular.module('phonecatApp', []); phonecatApp.controller('PhoneListCtrl', function($scope) { $scope.phones = [ {'name': 'Nexus S', 'snippet': 'Fast just got faster with Nexus S.'}, {'name': 'Motorola XOOM with Wi-Fi', 'snippet': 'The Next, Next Generation tablet.'}, {'name': 'MOTOROLA XOOM', 'snippet': 'The Next, Next Generation tablet.'} ]; });
(Demo)Each time the model data changes, our DOM list will be updated. How does Angular achieve this? Well, behind the scenes, he performs what we call dirty-checking.

The underlying idea of ​​dirty-checking is that at any given time the data can be changed, and the library needs to check how it has changed. In the case of an angular, this happens through the registration of the states of all the model data that needs to be monitored. He knows about the previous values ​​of the model and if they change, the corresponding event occurs. For the developer, the main profit lies in the fact that we work with native JS objects that are easy to maintain and merge. On the other hand, to the extent that the algorithm is very voracious, it can be a very expensive solution.

The costs of such operations are proportional to the total number of objects observed. I may need to do a lot of these checks. It is also possible that I will need a way to trigger dirty-checking events when the model “possibly” has changed. There are quite a few tricky tricks that the framework uses to implement such solutions. It is still not clear whether this decision will bring to mind.
The web ecosystem should have more opportunities to improve and develop its declarative mechanisms, for example
- Systems of models based on containers with external access to attributes through setters / getters
- Systems with autosave (saving changes to IndexedDB or localStorage)
- Container objects (for example, Backbone, Ember)
Container objects are a data storage mechanism in which the framework creates objects that store data inside themselves, access to which is provided by accessors (getters / setters), and also implements the ability to subscribe to any changes. This works quite well: the algorithm provides a fairly fast operation. An example of using such objects in Ember can be found below:
The cost of detecting changes in this case is proportional to the number of properties that change. Another problem is that you use objects of different types. Simply put, you convert the data that you receive from the server to install it into the observed object.
This is not particularly compatible with the existing JS code, since Most of the code assumes that it can interact with ordinary data (not wrapped in a container object,
etc., lane ), but not with these specialized objects.
Introduction to Object.observe ()
It would be really cool if we could get the best from these two universes: take the opportunity to observe data changes with support for ordinary objects (native JavaScript objects) and remove dirty-checking, and instead add some algorithm with good characteristics. Any one that will combine all these positive qualities and will be built into the platform. So, meet - Object.observe (), already ready to use!
It allows us to observe the object, change properties, and observe the change through change reports. But enough theory, let's look at the code!

Object.observe () and Object.unobserve ()
Let's imagine that we have a native JS object representing the model:
We can also declare a return function that will be called as soon as the object changes.
function observer(changes){ changes.forEach(function(change, i){ console.log('what property changed? ' + change.name); console.log('how did it change? ' + change.type); console.log('whats the current value? ' + change.object[change.name]); console.log(change);
Note: When the return function is called for the observer, the observed objects can be changed several times, so for each change, the new value and the current value are not necessarily the same.
We can observe such changes using Oo (), passing the observed object as the first argument and the return function as the second:
Object.observe(todoModel, observer);
Let's try to do something with our model:
todoModel.label = 'Buy some more milk';
Look in the console, we got some useful information! We know which property has changed, how it has been changed and what new value has been assigned to it.

Wow Goodbye dirty-checking! The inscription on your tombstone will be carved by Comic Sans. Let's change another property. This time completeBy:
todoModel.completeBy = '01/01/2014';
As we see, we again successfully received the change report:

Great, but what if now we decide to remove the “completed” property from our object:
delete todoModel.completed;

Now, as we can see, the change report includes the deletion information. As expected, the new property value is now undefined. So, now we know that we can track when new properties have been added or deleted. Simply put, a number of properties of an object (“new”, “deleted”, “reconfigured”) and its prototype chains.
As in any surveillance system, there must be a method for stopping the observation of changes in the object. In our case, this is Object.unobserve (), which has the same signature as Oo (), but can be called like this:
Object.unobserve(todoModel, observer);
As we can see below, any changes that were made after the last method call will no longer display a change report to the console.

Focus on properties
So, when we got acquainted with the basics, let's go back to the list of changes of the observed object.
Object.observe(obj, callback, opt_acceptList)
Let's go straight to the example to see how this can be used:
Now, if we remove the “label” property, a change notification will occur:
delete todoModel.label;
If you do not specify a list of applicable types for Oo (), then by default the “internal” object is transferred, which regulates the changes “add”, “update”, “delete”, “reconfigure”, “preventExtensions” (if the object cannot be changed, it will not observable).
Notifications
Oo () also has the notion of notifications (alerts). They have nothing to do with those annoying things that pop up on your phone, they are much more useful. Notifications are similar to
Mutation Observers (and a great
article on zag2art in Habré ). They occur during the completion of microtasks. In the context of the browser, they almost always occur at the end of the current event handler.
This time is extremely convenient due to the fact that the main work has already been done, and observers can begin to do their work without interfering with the basic logic. This is a great model for step-by-step event handling.
A workflow built using alerts looks something like this:

And now let's look at an example of how notifications can be used to notify about a change in the state of an object. Pay attention to the comments:

Here we display an alert when data changes (“update”), and everything, anything, actually, if this is indicated in the call to the notifier.notifyChange () method.
Years of experience in web development have taught us that synchronous operations are the first thing we learn, because the easiest way to manage just such operations. The problem is that it creates a potentially dangerous processing model (data processing model,
etc. trans. ). If you write code and, say, write “update object property”, you hardly want this to lead to the triggering of some arbitrary code in the middle of a function that can do whatever it wants.
Even on the part of the observer, you do not want any third-party function to be called from the middle of the current function, etc. Inconsistency and disconnection of function call sites is annoying, right? And add to this more error checking, and other situations that can make life difficult with this approach. As a result, we see that it is really difficult to work with such a model. The asynchronous approach is more difficult to understand, but still, today, there is nothing better than it.
The solution to this problem can be a synthetic record change.
Synthetic change of records
Simply put, if you need to access an accessor or a calculated property (you can read about calculating properties
here ), you are responsible for creating an alert when this property changes. This adds a bit of work, but is rather a feature than an inconvenience. Your alerts will be delivered along with alerts about changes to other properties and will look like this:

Monitoring accessors and changes to computed properties can be done using notifier.notify (this is also included in the Object.observe specification). Many systems for monitoring changes should provide information about the changed values, and, frankly, we have quite a few ways to do this. Object.observe does not impose the "right" path on us.
I think that web developers can expect any ready-made solutions from libraries to help with the notification of changes in calculated properties.
Now let's move on to the following example, which illustrates the creation of the “Circle” class. The bottom line is that we have a circle and its radius property. In our example, the radius will be an accessor, and when its value changes, an event will occur, notifying of this. It will be delivered along with all other object change alerts.
Let's see how our code will work in DevTools:
function Circle(r) { var radius = r; var notifier = Object.getNotifier(this); function notifyAreaAndRadius(radius) { notifier.notify({ type: 'update', name: 'radius', oldValue: radius }) notifier.notify({ type: 'update', name: 'area', oldValue: Math.pow(radius * Math.PI, 2) }); } Object.defineProperty(this, 'radius', { get: function() { return radius; }, set: function(r) { if (radius === r) return; notifyAreaAndRadius(radius); radius = r; } }); Object.defineProperty(this, 'area', { get: function() { return Math.pow(radius, 2) * Math.PI; }, set: function(a) { r = Math.sqrt(a)/Math.PI; notifyAreaAndRadius(radius); radius = r; } }); } function observer(changes){ changes.forEach(function(change, i){ console.log(change); }) }

Properties of accessors
A small note about the properties of the accessor. Earlier, we talked only about observing changes in property values, but did not at all mention such behavior for accessors or calculated properties. The reason for this is the fact that JavaScript actually does not have the ability to track changes in values ​​for the accessors, because in fact they are only a collection of functions.
If you’ve already worked with accessories, you can imagine how they work: they simply provide a set of functions that give access to properties, and nothing more.
Monitoring multiple objects with one return function
Another possible pattern when working with Oo () is the notation for using object monitoring with a single return function. This allows you to use this function as an observer function for any number of different objects. The returned function will provide each time a complete set of changes for all the objects that it tracks (this will occur at the end of all microtasks, see Mutation Observers).

Major changes
Perhaps you are working on a truly great project and are regularly forced to face major changes.
Oo () helps with this by using two specific functions: notifier.performChange () and notifier.notify (), which we have already mentioned.

Let's look at an example of how large-scale changes could be described using a Thingy object using some mathematical functions (multiply, increment, incrementAndMultiply). Every time we use a function, it tells the system that the work collection includes a certain type of change.
For example: notifier.performChange ('foo', performFooChangeFn);
function Thingy(a, b, c) { this.a = a; this.b = b; } Thingy.MULTIPLY = 'multiply'; Thingy.INCREMENT = 'increment'; Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply'; Thingy.prototype = { increment: function(amount) { var notifier = Object.getNotifier(this);
We declare two observers for our facility: one to monitor all changes and another to report on specific changes that we described above (Thingy.INCREMENT, Thingy.MULTIPLY, Thingy.INCREMENT_AND_MULTIPLY). var observer, observer2 = { records: undefined, callbackCount: 0, reset: function() { this.records = undefined; this.callbackCount = 0; }, }; observer.callback = function(r) { console.log(r); observer.records = r; observer.callbackCount++; }; observer2.callback = function(r){ console.log('Observer 2', r); } Thingy.observe = function(thingy, callback) {
Well, now we can play around a bit with the code. Let's announce the new thingy: var thingy = new Thingy(2, 4);
Put it under surveillance and make a few changes. Cool, cool!
Everything that is inside the “function performed” will be considered work with “a large number of changes”. Observers who accept "big changes" will receive only them. The remaining observers will receive the remaining changes.Array Monitoring
We talked about observing object changes, but what about arrays? Great question! By the way, every time they say “An excellent question” to me, I never hear the answer, because too focused on congratulating ourselves on such a good question, but we digress :) We have new methods for working with arrays!Array.observe()
This is a method that works with a large number of changes to the object itself, for example splice, unshift, or something else that changes its length, such as splice.For this, he uses the notifier.performChange("splice",...)
following example of where we observe the “array” model and in the same way we get back the list of changes when actions are performed on the model that change its data: var model = ['Buy some milk', 'Learn to code', 'Wear some plaid']; var count = 0; Array.observe(model, function(changeRecords) { count++; console.log('Array observe', changeRecords, count); }); model[0] = 'Teach Paul Lewis to code'; model[1] = 'Channel your inner Paul Irish';

Performance
About computational speed Oo () can be thought of as the speed of reading the cache. Generally speaking, a cache is an excellent choice when (in order of importance):- Read frequency is higher than write frequency
- When you have the opportunity to create a cache that will sacrifice write time in the direction of a fixed time for read operations
- Constant latency for write operations is acceptable.
Oo () is designed for use cases like the first.Dirty-checking requires you to keep copies of all the data you are watching. This means that you get a memory drawdown that you never get with Oo (). Dirty-checking is a kind of gag, which also creates a kind of unnecessary abstraction, which as a result creates unnecessary complexity in applications.Why?
Because dirty-checking is started every time the data "could" be changed. This is not a reliable method of such verification and has significant drawbacks, for example, the race between the rendering code (and so on) and the computational code (after all, everyone knows that in JS one stream is used for both the interface and the calculations?). For dirty-checking, a global registry of observers is also required, thereby creating the risk of memory leaks, etc., which Oo () allows to avoid.Let's take a look at some numbers.The benchmarks below (available on GitHub ) allow us to compare dirty-checking and Oo (). They are represented as a graph with the Observed-Object-Set-Size abscissa and the Number-Of-Mutations ordinate.The main results are that the performance of dirty-checking is proportional to the number of objects observed, while the performance of Oo () is proportional to the number of mutations that we made.Dirty-checking

Object.observe ()

Object.observe () for older browsers
Cool, Oo () can be used in Chrome 36 beta, but what about other browsers? We will help you. Observe-JS is a polymer for Polymer that will use the native implementation as soon as it appears, but it also includes some useful things on top of this. He proposes to use a generalized view of the objects of observation and reports on general changes. Here are a couple of useful things he suggests:1) You can watch the “ways”. This means that you can say "hey, I want to monitor foo.bar.baz" for the selected object and it will notify you of the change in properties as soon as it happens. If the path is unavailable, it will return undefined
.Example of observing the value along the path of the specified object: var obj = { foo: { bar: 'baz' } }; var observer = new PathObserver(obj, 'foo.bar'); observer.open(function(newValue, oldValue) {
2) It will alert you about changing the length of the arrays. In our case, changing the array length is the minimum number of splice operations we need to do with the array in order to transfer it from the old state to the new (changed) state.An example of notification of such changes regarding an array as the minimum set of splice operations: var arr = [0, 1, 2, 4]; var observer = new ArrayObserver(arr); observer.open(function(splices) {
Framework and Object.observe ()
As we have said, Oo () gives frameworks and libraries great opportunities to improve the performance of their data-buying mechanism in browsers that support this innovation.Yehuda Katz and Eric Brin from Ember approved Oo () support in Ember’s nearest roadmaps. Misko Hervey (from Angular) also wrote in the draft of his Angular 2.0 documentation about improving change detection.Yehuda Katz and Erik Bryn from Ember confirmed that he was in Ember's near-term roadmap. Angular's Misko Hervy wrote a design doc on Angular 2.0's improved change detection. I think that it is most likely to expect movement in this direction when this feature appears in the Chrome 36 stable package.Results
Oo () is a powerful innovation for the web platform that you can use today.We hope that this functionality will soon appear in other browsers, allowing JavaScript frameworks to get some performance benefits with new native features of objects and monitoring them. In addition to Chrome 36, this functionality will also be available in the next release of Opera.Well, now you can go and tell JS frameworks about Object.observe () and how they can use it to improve their data-binding mechanism.It looks like really amazing times are coming!Resources used:
UPD: “Binding” turned into “binding”UPD 2: Comments were translated into RussianUPD 3: Corrected grammatical and punctuation errors. Thank you, Mingun