How to create an Observer in Javascript
Javascript throws the power at you, but the control is all yours!
We talk about observer here. Like in simple English, it literally means you’re listening / you want to listen to something. You haven’t decided yet what to listen. You just expressed the interest of listening something. And you’re looking for an offer.
In computer language, we mostly deals with objects, properties, methods, events and data. What you can listen, you can also handle it. The handler is nothing but the observer function. It deals with the data which you receive as an argument in the observer function. Unless you’re dealing with the built-in events, the handler function is a custom implementation most of the time.
Let’s take a simple example to digest this first. Below snippet reads how to attach an observer to the click event of the window object.
// Observer for window click event
var clickObserver = function(event) {
// target is the source of the element which is clicked
let target = event && event.target; // Handle the custom logic here...
};// To attach the observer callback
window.addEventListener('click', clickObserver);
Like you attach an observer, you can also detach the same from the window object.
// To attach the observer callback
window.addEventListener('click', clickObserver);
With some good understanding you can write observers to any event which the object produces. But the intent of the article is also to learn how to deal with built-in functions.
For example, let’s take window.history object which has several events and callback functions invoked on window transitions. In this, it’s not enough if you only observe the history object handlers, but also you maintain it’s scope so it can be referred to call the actual listener while we implement some custom actions to attach to it. If we not do so, there will be an interruption in the actual behaviour. This will cause serious damage to the page.
PS: None of these code snippets shared in this article is production ready.
// The below function returns a generic observer object which allows you to deal with the built-in object events while observingvar GenericObserver = (function() {
var instance,
_data = {},
_attach = function(scope, eventName) { // Assign the original event function
_data[eventName] = scope[eventName];
// Attach the new event function
scope[eventName] = function() { // Create the invoker event
var evt = _data[eventName].apply(scope, arguments); // Call the overrided observer
var observerEvent = scope[`${eventName}_observer`];
if (typeof observerEvent === 'function') {
observerEvent();
}
// Return the invoker event
return evt;
} },
_detach = function(scope, eventName) {
if (_data[eventName]) { // Detach the new event function
delete scope[eventName];
// Assign the original event function
scope[eventName] = _data[eventName]; // Delete the event data
delete _data[eventName];
}
};
// Constructor for Observer
function Observer() {
if (instance) {
return instance;
}
instance = this;
} // Observer Prototype Method 'attach'
Observer.prototype.attach = function(scope, eventName, observer)
{
if (scope[eventName] && observer) {
scope[`${eventName}_observer`] = observer;
_attach(scope, eventName);
}
}; // Observer Prototype Method 'detach'
Observer.prototype.detach = function(scope, eventName) {
if (scope[eventName] && scope[`${eventName}_observer`]) {
scope[`${eventName}_observer`] = void 0;
_detach(scope, eventName);
}
}; return new Observer();
})();
Explanation of the above code
Step 1: Defined a singleton object so we won’t end up creating multiple instances.
Step 2: We also added two private functions _attach
and _detach
which is self explanatory. These method let us bind the methods of the built-in object. It carries two argument as input.
scope
Actual built-in objecteventName
Actual event name of the built-in object
Step 3: _attach
method collects the actual method from the built-in object and keep a local reference. It then creates a new inline handler to attach to the built-in object so as to replace the actual handler. When the event is triggered it calls the inline handler. This then calls the built-in object actual handler and apply its scope. Now there is no interruption in the actual behaviour of the built-in object. Add all your custom implementation below this as given in the snippet
Step 4: _detach
method just do the reverse of _attach
method. It removes the inline handler and add the actual built-in handler back to the scope of the built-in object
Step 5: These inline functions are restricted for outside access and it’s private to your GenericObserver
Now add two public prototype methods to this singleton instance so you can safely access this from outside.
Step 6: Invocation to these methods can be implemented as follows
// To attach a generic observer to history push state
GenericObserver.attach(window.history, 'pushState', () => {
// Inside Push State
});// To attach a generic observer to history replace state
GenericObserver.attach(window.history, 'replaceState', () => {
// Inside Replace State
});// To detach the push state observer
GenericObserver.detach(window.history, 'pushState');// To detach the replace state observer
GenericObserver.detach(window.history, 'replaceState');