Introduction to Object.observe

    Ravi
    Ravi
    Share

    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:

    object.observe-example-1

    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:

    object.observe-example-2

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

    object.observe-example-3

    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!

    Frequently Asked Questions (FAQs) about Object.observe

    Why was Object.observe deprecated?

    Object.observe was deprecated because it was found to have several limitations and issues. It was not able to observe changes made within a function, and it was also not able to track changes made to an object’s prototype. Additionally, it was found to be inefficient in terms of performance, as it required a lot of resources to track changes in large objects. The deprecation was also influenced by the introduction of new features in ES6, such as Proxies and Reflect, which provide more efficient ways to observe and react to changes in objects.

    What are the alternatives to Object.observe?

    With the deprecation of Object.observe, developers have turned to other methods for observing changes in objects. One of the most popular alternatives is the use of ES6 Proxies. Proxies allow you to define custom behavior for fundamental operations on objects, such as property lookup, assignment, enumeration, function invocation, and more. Another alternative is the use of libraries such as MobX or Vue.js, which provide their own mechanisms for observing changes in objects.

    How do ES6 Proxies work as an alternative to Object.observe?

    ES6 Proxies provide a way to customize the behavior of fundamental operations on objects. A Proxy is created with two parameters: the target object and a handler object. The handler object defines ‘traps’ for various operations on the target object. When these operations are performed, the corresponding trap in the handler is triggered, allowing custom behavior to be executed. This makes Proxies a powerful tool for observing and reacting to changes in objects.

    What are the benefits of using ES6 Proxies over Object.observe?

    ES6 Proxies offer several advantages over Object.observe. They provide a more flexible and powerful way to observe and react to changes in objects. With Proxies, you can define custom behavior for a wide range of operations, not just changes to properties. Proxies also perform better than Object.observe, especially when dealing with large objects. Furthermore, Proxies are a part of the ES6 standard, which means they are supported by all modern browsers.

    Can I still use Object.observe in my projects?

    While it is technically possible to still use Object.observe in your projects, it is strongly discouraged. Object.observe has been deprecated and removed from the JavaScript standard, which means it is no longer maintained and may not be supported by all browsers. Using deprecated features can lead to compatibility issues and other problems in your projects. It is recommended to use alternatives such as ES6 Proxies or libraries like MobX or Vue.js.

    How can I migrate from Object.observe to ES6 Proxies?

    Migrating from Object.observe to ES6 Proxies involves replacing the Object.observe calls with Proxy objects. Instead of observing changes to an object’s properties, you define traps for the operations you want to observe in the handler object of the Proxy. This can involve some refactoring of your code, but it provides a more flexible and efficient way to observe changes in objects.

    What is the performance impact of using ES6 Proxies?

    ES6 Proxies are generally more efficient than Object.observe, especially when dealing with large objects. However, like any feature, they should be used judiciously. Creating a Proxy for every object in your application can lead to performance issues. It’s best to use Proxies only when necessary and to keep the handler objects as lightweight as possible.

    Are there any limitations or issues with using ES6 Proxies?

    While ES6 Proxies are a powerful tool, they do have some limitations. For example, they cannot be used to observe changes to the length property of arrays. Also, Proxies do not provide a way to observe changes to an object’s prototype. However, these limitations can often be worked around by using other features of ES6, such as Reflect.

    How do libraries like MobX or Vue.js handle object observation?

    Libraries like MobX or Vue.js provide their own mechanisms for observing changes in objects. For example, MobX uses observable properties and computed values to track changes, while Vue.js uses a reactive data model. These libraries provide a high-level API for object observation, making it easier to use than raw ES6 Proxies.

    What is the future of object observation in JavaScript?

    The future of object observation in JavaScript is likely to be shaped by the continued evolution of the language and the development of libraries and frameworks. Features like ES6 Proxies and Reflect provide powerful tools for observing and reacting to changes in objects, and libraries like MobX and Vue.js build on these tools to provide high-level APIs for object observation. As the language and ecosystem continue to evolve, we can expect to see more efficient and flexible ways to observe changes in objects.