Watch an object for changes in Vanilla JavaScript
A Javascript object often needs to be watched for changes. Depending on such changes we might be looking to fire certain events. Sometimes, we would need to observe an object for debugging purpose as well.
Any change to an object that is not caused by a DOM event cannot be caught by any event listener. Hence, there needs to be a way to observe an object for changes, be able to access the changed values, and fire events based on any such change.
Over the period of evolution of Javascript, there has been many ways by which the above can be achieved. In this article we will be discussing some of the most popular ways of watching an object over the times. Some of these functions have been deprecated over time while some of them are still used.
Method 1: Polling
The first and most crude way of watching a Javascript object, or any variable is by polling. Javascript offers the functions setInterval and setTimeout that can be used to check the values of any number of variables. However, in order for the watch to become accurate, the polling needs to happen very frequently, ie ideally every millisecond or every 10ms. Running a piece of code that frequently is extremely expensive in terms of compiler cost, and hence polling is hardly ever used to watch a variable. Polling however, can be used only in case that we know that the value of the variable will change at regular and big intervals, say every minute.
const obj = { foo: “bar”};function repeatChecking(oldValue) { if(oldValue) { setInterval(() => { if(obj.foo !== oldValue) { console.log(`Value of ${oldValue} to ${obj.foo}`); oldValue = obj.foo; } }, 10); }}repeatChecking(obj.foo);obj.foo = “I have changed.”;setTimeout(()=> { obj.foo = “I have changed again.”;}, 1000);
In order to clear the setInterval, assign it to a variable and clear the interval using clearInterval() function.
Method 2: Object.prototype.watch()
The watch function watches any property of an object for changes. This also includes the first assignment of value to the property.
The watch function includes a handler which has access to the old value and the new value of the watched property of the object.
obj.watch(“foo”, function (oldValue, newValue) {console.log(`Value of foo changed from ${oldValue} to ${newValue}`);});
If the given property, on which the watcher is set, is deleted, the watch doesn’t disappear, rather it remains intact. If the property is recreated in the future, the watch continues functioning in the same way. In order to remove the watcher, one has to call the unwatch() function.
obj.watch(“foo”, function (oldValue, newValue) {console.log(`Value of foo changed from ${oldValue} to ${newValue}`);});obj.foo = “I have changed.”;obj.foo = “I have changed again.”;delete obj.foo;obj.foo = “I am back from dead!”;obj.unwatch(‘foo’);obj.foo = “Finally, I am no one.”
The watch function is only supported on Firefox browsers, and have been currently deprecated there as well. It was never advisable to use the watch function except for debugging purposes alone.
Method 3: Object.observe()
This function was also used to asynchronously observe the changes occurring in an object. In this function, the handler function can filter out the types of changes it is looking to observe. The handler also gets access to information like what type of change has occurred and not just the changed value.
For eg.
var obj = {
foo: "Initial Value"
};
Object.observe(obj, function(changes) {
console.log(changes);
});
On adding a new property to the object, updating an existing property and deleting a property:
obj.newProp = "I am new Property";
// [{name: 'newProp', object: <obj>, type: 'add'}]obj.foo = 'New Value of Foo';
// [{name: 'foo', object: <obj>, type: 'update', oldValue: 'Initial Value'}]delete obj.newProp;
// [{name: 'newProp', object: <obj>, type: 'delete', oldValue: 'I am new Property'}]
Object.observe() function, too, has been deprecated and removed from all current browsers.
Method 4: Register a custom listener to the object utilizing the getter and setter methods.
If we define our object to set value to properties through setters, then we can also create a listener as one of the properties. A custom function can be assigned as the listener defined within the object. This custom listener can be utilized to listen to value changes to the properties of the object.
const obj = { fooValue: “bar”, get foo() { return this.fooValue; }, set foo(val) { this.fooValue = val; this.fooListener(val); }, fooListener: function (val) {}, registerNewListener: function (externalListenerFunction) { this.fooListener = externalListenerFunction; },};obj.registerNewListener((val) => console.log(`New Value: ${val}`));
This listener successfully listens to changes happening to the property, because the listener is triggered from the setter function, every time a new value is assigned to the given property. However, if we use delete to delete the property of the object, it deleted the getter and setter as well. So, if the property is re-added, the listener cannot listen to the new property created.
obj.foo = “I have changed.”;obj.foo = “I have changed again.”;delete obj.foo;obj.foo = “I am back from dead!”;
The output comes something like this:
New Value: I have changed.
New Value: I have changed again.
Method 5: Using the Proxy Object
A Proxy object is used to define custom behavior on fundamental operations. A Proxy virtualizes an object and customizes its fundamental methods which includes the getters and setters. By using a proxy, we can make sure that we do not make any changes to the core structure of the original object, yet customize its method to trigger a custom event every time a change occurs.
const obj = { foo: “bar”,};const objProxy = new Proxy(obj, { set: function (target, key, value) { console.log(`${key} set from ${obj.foo} to ${value}`); target[key] = value; return true; },});
A proxy, however, has varied applications and can be used for many things in Javascript. For a more detailed study on proxies, visit: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy