English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Deep Understanding of the Observer Pattern in JavaScript

Overview

The observer pattern is also known as the publish-subscribe pattern (Publish/Subscribe), it defines a one-to-many relationship, allowing multiple observer objects to listen to a certain subject object at the same time. When the state of the subject object changes, it notifies all observer objects, enabling them to automatically update themselves. In theory, if we are not writing slightly lower-level code, we may not need it. However, it makes the code more flexible, more organized, reduces redundant code, and facilitates modular and functional development.

Benefits of using the observer pattern:

  1. Supports simple broadcast communication, automatically notifying all subscribed objects.
  2. After the page is loaded, the target object can easily establish a dynamic association with the observer, which increases flexibility.
  3. The abstract coupling relationship between the target object and the observer can be extended and reused independently.

Introduction

In front-end business, the use of custom events may be more frequent.
Actually, the event handling in browsers is also based on the observer pattern.

div.onclick = function click() {
 console.log('click')
}

Here the function click subscribes to the click event of the div. When our mouse click operation triggers the event, the corresponding function will execute. This function click is an observer.

Concretization of understanding

In fact, the pure code implementation can also be understood. However, everything is connected, and these programming patterns were also originally derived from life experiences, so a concrete understanding is also very important.

Let's take the example of a wedding banquet. For example, if one of your good friends is getting married, the event of 'getting married' does not happen every day, it only happens once or twice in a lifetime, so our 'attending his wedding' definitely does not happen every day, but only at specific times. I can't ask him every day, 'Are you getting married today, and I will come to the banquet'. Once or twice is okay, but asking every day is silly. Suppose you are a single person who can't find a partner, and you are asked like this every day, you might kill you...

So here there needs to be an event published, that is, 'notify you'.

As an observer, I subscribe to his 'marriage' event, which means we are good friends, and I will definitely attend his wedding because we have already agreed. So I am the observer, and 'I attend the wedding' is the corresponding action. When I subscribe to the 'marriage' event, I don't need to ask him every day, I can do whatever I want, go out to meet girls, have dinner, watch movies, and so on.

When he publishes the 'marriage' event and notifies me, I will go to the specific behavior function 'attend the wedding banquet' at the right time...

//Mock code
//I subscribed to the 'marry' event
wo.on('marry',function(){
 //To attend the wedding banquet
}
//And then he publishes. For example, the browser's click event
// Then my function will execute

Decoupling/Module/Functionality

In fact, there is a need for a similar intermediate service in the code, a middleman to manage publishing and subscribing.

For example, the event handler in the browser provides an interface for subscription and then receives 'event' signals to publish to you. This makes the JavaScript code interact with the browser, creating a connection between the two originally different things.

In my opinion, the biggest benefit of the observer pattern is decoupling, which allows us to separate the code into functions and modules, making it clearer, reducing development costs, and easier to maintain.

For example:

1In our project, the view presentation layer and the model (data processing) logic layer are initially written for the page, AJAX, string concatenation, and then a request is made to an interface to combine it with the DOM. It's possible that within one JS file and one function, there are requests to interfaces and also the responsibility for the view's display.

var xhr = new XMLHttpRequest ()
 xhr.open('get', url)
 xhr.onreadystatechange = function () {
 if(this.readyState !== 4) return
 if(this.status === 200) {
 divs.innerHTML = '<p>' + this.response + </p>'
 //
 }
 }
 xhr.responseType = 'json'
 xhr.send(null)

In fact, it should be separated into requesting and displaying/rendering.

//Request
function getData () {
 var xhr = new XMLHttpRequest ()
 xhr.open('get', url)
 xhr.onreadystatechange = function () {
 if(this.readyState !== 4) return
 if(this.status === 200) {
 this.emit('render')
 // Publish
 }
 }
 xhr.responseType = 'json'
 xhr.send(null)
}
//Rendering
function view () {}
xhr.on('render', view)

Directly in the status code2I can also do it by placing a callback at 00. But, if I have two or more rendering functions that handle different things, do I have to change the function each time? Isn't the same request process still to be written?

In terms of observer

function view1 () {}
function view2 () {}
function view3 () {}
function view4 () {}
if(I want to render view1) {
 xhr.on('render', view1) //Subscribe
 xhr.on('render', view2)
}
 xhr.on('render', view3)
 xhr.on('render', view4)
}

The benefit lies in my getData function, which is only responsible for requesting data, and then it will expose an interface for me to add methods. So my getData is relatively a complete functional module, and even if I have more situations, the code inside my getData will not change.

Sometimes we often change the code we have written before to add a new feature for business implementation, which leads to the original functional module being changed beyond recognition.

And there will be a lot of repetitive code.

Process? or Module?

Of course, it's quite difficult to encapsulate a good and complete functional module, but we at least need to have a starting point.

Subscribe to add methods, and the event pool is executed when published.

2, MV* Class framework

MVC is also a design pattern, and observers are also applied here.

It also internally uses various publish-subscribe mechanisms, as if it is an observer model, thus achieving a simulated in-memory DOM change and calculating which DOM node should change. Of course, the specific implementation involves many things... I won't go into detail...

3, redux

A simple implementation of a createStore function

//This is a factory function that can create a store
const createStore = (reducer) => {
 let state; // Define the stored state
 let listeners = [];
 // The role of getState is very simple, which is to return the current state
 const getState = ()=> state;
 //Define a dispatch function
 //When this function is called from the outside, it will modify the state
 const dispatch = (action)=>{
 //Call the reducer function to modify the state, return a new state and assign it to this local state variable
 state = reducer(state,action);
 //Call the listener functions in turn, notifying all listener functions
 listeners.forEach(listener => listener());
 }
 //Subscribe to this state function, and remember to call this listener function when the state changes
 const subscribe = function(listener){
 //First add this listener to the array
 listeners.push(listener);
 //Return a function that, when called, removes this listener function from the listener array
 return function(){
  listeners = listeners.filter(l => l != listener);
 }
 }
 //The default dispatch calls once to assign an initial value to the state
 dispatch();
 return {
 getState,
 dispatch,
 subscribe
 }
}
let store = createStore(reducer);
//Render data to the interface
const render = () => {
 document.body.innerText = store.getState();
}
// Subscribe to state change events, and use the listener function when the state changes
store.subscribe(render);
render();
var INCREASE_ACTION = {type: 'INCREMENT'};
document.addEventListener('click', function (e) {
 //Trigger an Action
 store.dispatch(INCREASE_ACTION);
}

4The role of EventEmitter in node.js: Most of the time, we do not use EventEmitter directly, but inherit it in objects. Core modules that support event response, including fs, net, and http, are all subclasses of EventEmitter.

Implement a class that can publish and subscribe

'use strict'
class EmitterEvent {
 constructor() {
 //Constructor. Create an event pool on the instance
 this._event = {}
 }
 //Subscription
 on(eventName, handler) {
 // According to eventName, there is a corresponding event array in the event pool,
 Add it if it exists, or create a new one if it doesn't.
 // It should be more rigorous to judge the type of handler, whether it is a function
 this._event[eventName].push(handler)
 else {
 }
 this._event[eventName] = [handler]
 }
 }
 emit(eventName) {
 // Find the corresponding array according to eventName
 var events = this._event[eventName];
 // Take the parameters passed in to facilitate the execution of the function
 var otherArgs = Array.prototype.slice.call(arguments,1)
 var that = this
 if(events) {
 events.forEach((event) => {
 event.apply(that, otherArgs)
 }
 }
 }
 // Unsubscribe
 off(eventName, handler) {
 var events = this._event[eventName]
 if(events) {
 this._event[eventName] = events.filter((event) => {
 return event !== handler
 }
 }
 }
 // After subscription, the subscription is automatically canceled after the emit is executed once
 once(eventName, handler) {
 var that = this
 function func() {
 var args = Array.prototype.slice.call(arguments, 0)
 handler.apply(that, args)
 this.off(eventName, func)
 }
 this.on(eventName, func)
 }
}
var event = new EmitterEvent()
function a (something) {
 console.log(something,'aa-aa')
}
function b (something) {
 console.log(something)
}
 event.once('dosomething',a)
 event.emit('dosomething', 'chifan')
 //event.emit('dosomething')
// event.on('dosomething',a)
// event.on('dosomething',b)
// event.emit('dosomething','chifan')
// event.off('dosomething',a)
// setTimeout(() => {
// event.emit('dosomething','hejiu')
// },2000)

When we need to use it, we just need to inherit the EmitterEvent class. The instance to be operated can be used with the on, emit method, that is, it can be used with publishing and subscribing. For example, XHR, component...

Summary

That's all for this article. I hope the content of this article can bring some help to everyone's learning or work. If you have any questions, you can leave a message for communication.

Statement: The content of this article is from the Internet, and the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously. This website does not own the copyright, has not been manually edited, and does not assume any relevant legal responsibility. If you find any content suspected of copyright infringement, please send an email to: notice#oldtoolbag.com (When reporting, please replace # with @) for complaints, and provide relevant evidence. Once verified, this site will immediately delete the infringing content.

You May Also Like