preface

In the previous two self-test lists, I shared a lot of JavaScript basics with you. You can review them together

This article is a content I shared with “modern JavaScript commandos” in our team. The second phase of learning content is “Design Pattern” series. I will organize the knowledge I am responsible for sharing into the output of the article.

“Modern JavaScript Commandos”

  1. “Beginner intermediate Front-end JavaScript Self-test List – 1”
  2. “Beginner intermediate Front-end JavaScript Self-test List – 2”

I. Model introduction

1. Background

It is common in software systems to encounter the requirement that when the state of an object changes, some of its related objects must also change accordingly. This is to establish an “object-to-object dependency” in which changes in one object “automatically notify other objects” and the other objects “react accordingly.”

We will change the object known as the “target”, will be notified of the object referred to as the “observer”, “an observable can correspond to more than one observer”, and there is no correlation between the observer, then can according to need to add and remove the observer, makes the system more easy to expand, this is the background of the observer pattern.

2. Concept introduction

Observer Pattern: Defines a “one-to-many dependency” between objects so that whenever an object’s state changes, its dependent objects are notified and automatically updated. The observer pattern is an object behavior pattern.

3. Life scenarios

All browser events (mouse hover, keystroke, etc.) are examples of observer mode.

In addition:

As we subscribe WeChat public, “the front study hall” (” target “), when the “front-end study hall” mass graphic, all public, fans are received (” observer “) this article (events), the content of this article is the publisher custom (custom events), fan to make a specific operation after reading (such as: thumb up, collection, attention, etc.).

Observer mode. PNG

Ii. Model characteristics

1. Pattern composition

In observer mode, the following roles are usually included:

  • Goal: the Subject
  • Observation target: ConcreteSubject
  • An Observer
  • ConcreteObserver: ConcreteObserver

2. The UML class diagram

UML class diagrams

Source: TypeScript Design Patterns observer Patterns

3. The advantages

  • The observer mode can achieve “separation of presentation layer and data logic layer” and “reduce the coupling degree between observation target and observer”.
  • The observer mode supports “simple broadcast communication” and “automatic notification” of all subscribed objects;
  • The observer mode “meets the requirements of the” open and close principle “;
  • The abstract coupling between the observation target and observer can be “individually extended and reused”.

4. The drawback

  • When an observation target “has multiple direct or indirect observers”, the process of notifying all observers will take a lot of time;
  • When there is a “loop dependency” between the observing target and observer, the observing target triggers a loop call between them, which can “cause the system to crash.”
  • The observer model lacks a mechanism to let the observer know how the observed object has changed, but only that the observed object has changed.

Three, use scenarios

The observer mode can be used when:

  • In an abstract model, the behavior of one object is “dependent” on the state of another object. That is, when the state of “target object” changes, it will directly affect the behavior of “observer”;
  • An object needs to tell other objects to react without knowing who those objects are.
  • You need to create A chain of triggers in the system where the behavior of object A affects object B, and the behavior of object B affects object C… You can use observer mode to create a chain trigger mechanism.

4. Practical examples

1. Simple example

  1. Define “Observing Target Interface” (Subject) and “Observer Interface” (Observer)
// ObserverPattern.ts



// Observe the target interface

interface Subject {

  addObserver: (observer: Observer) = > void;

  deleteObserver: (observer: Observer) = > void;

  notifyObservers: (a)= > void;

}



// Observer interface

interface Observer {

  notify: (a)= > void;

}

Copy the code
  1. Define “ConcreteSubject”
// ObserverPattern.ts



// Observe the target class

class ConcreteSubject implements Subject{ 

  private observers: Observer[] = [];

 

  // Add an observer

  public addObserver(observer: Observer): void {

    console.log(observer, " is pushed~~");

    this.observers.push(observer);

  }



  // Remove the observer

  public deleteObserver(observer: Observer): void {

    console.log(observer, " have deleted~~");

    const idx: number = this.observers.indexOf(observer);

    ~idx && this.observers.splice(idx, 1);

  }



  // Notify the observer

  public notifyObservers(): void {

    console.log("notify all the observers ".this.observers);

    this.observers.forEach(observer= > { 

      // The notify method can be called with specified parameters

      observer.notify();

    });

  }

}

Copy the code
  1. Define a “ConcreteObserver class”
// ObserverPattern.ts



/ / specific view

class ConcreteObserver implements Observer{

  constructor(private name: string) {}



  notify(): void {

    // Other logic can be handled

    console.log(`The ${this.name} has been notified.`);

  }

}

Copy the code
  1. The test code
// ObserverPattern.ts



function useObserver() :void {

  const subject: Subject = new ConcreteSubject();

  const Leo   = new ConcreteObserver("Leo");

  const Robin = new ConcreteObserver("Robin");

  const Pual  = new ConcreteObserver("Pual");

  const Lisa  = new ConcreteObserver("Lisa");



  subject.addObserver(Leo);

  subject.addObserver(Robin);

  subject.addObserver(Pual);

  subject.addObserver(Lisa);

  subject.notifyObservers();

  

  subject.deleteObserver(Pual);

  subject.deleteObserver(Lisa);

  subject.notifyObservers();

}



useObserver();

Copy the code

The complete demo code is as follows:

// ObserverPattern.ts



interface Subject {

  addObserver: (observer: Observer) = > void;

  deleteObserver: (observer: Observer) = > void;

  notifyObservers: (a)= > void;

}



interface Observer {

  notify: (a)= > void;

}



class ConcreteSubject implements Subject{ 

  private observers: Observer[] = [];



  public addObserver(observer: Observer): void {

    console.log(observer, " is pushed~~");

    this.observers.push(observer);

  }



  public deleteObserver(observer: Observer): void {

    console.log(observer, " have deleted~~");

    const idx: number = this.observers.indexOf(observer);

    ~idx && this.observers.splice(idx, 1);

  }



  public notifyObservers(): void {

    console.log("notify all the observers ".this.observers);

    this.observers.forEach(observer= > { 

      // The notify method can be called with specified parameters

      observer.notify();

    });

  }

}



class ConcreteObserver implements Observer{

  constructor(private name: string) {}



  notify(): void {

    // Other logic can be handled

    console.log(`The ${this.name} has been notified.`);

  }

}



function useObserver() :void {

  const subject: Subject = new ConcreteSubject();

  const Leo   = new ConcreteObserver("Leo");

  const Robin = new ConcreteObserver("Robin");

  const Pual  = new ConcreteObserver("Pual");

  const Lisa  = new ConcreteObserver("Lisa");



  subject.addObserver(Leo);

  subject.addObserver(Robin);

  subject.addObserver(Pual);

  subject.addObserver(Lisa);

  subject.notifyObservers();

  

  subject.deleteObserver(Pual);

  subject.deleteObserver(Lisa);

  subject.notifyObservers();

}



useObserver();

Copy the code

2. Vue. Js data bidirectional binding implementation principle

In Vue.js, when we modify the data shape, the view is updated. This is the two-way data binding (also known as the reactive principle) of vue.js, which is one of the most unique features of vue.js. If you are not sure about the two-way data binding of vue.js, you are advised to read the section of the official documentation called “Deep Responsive Principles” first.

2.1 Principles

The official website provides this flow chart, which describes the entire flow of vue.js responsive system:

Photo from: vue.js official websiteDeep into the responsivity principle”

In vue.js, there is a Watcher instance for each component instance, which records data properties “touched” during component rendering as Collect as Dependency. Watcher is then notified (the Notify process) when the setter for the dependency fires, causing its associated component to re-render (the Trigger re-render process) — a typical observer pattern.

This interview question examines the interviewees’ understanding of vue.js underlying principle, the ability to implement the observer mode and a series of important JS knowledge points, which has a strong comprehensive and representative.

2.2 Components

There are three key players in the implementation logic of vue.js data bidirectional binding:

  • Observer: The Observer is not only a subscriber (” needs to listen for data changes “) but also a publisher (” forwards monitored data “).
  • Watcher (subscriber) : The Watcher object is the true subscriber, and ** Observers forward data to the Watcher object. When watcher receives the new data, it performs the view update.
  • Compile (compiler) : A role specific to the MVVM framework that scans and parses each node element instruction, handles data initialization of the instruction, creation of subscribers, and so on.

The coordination process of the three is shown in the figure:Image from: “core Principles and Application practice of JavaScript Design Patterns”

2.3 Implement the core code Observer

First we need to implement a method that iterates over the data object we want to listen on, adding custom getters and setters to its properties. Whenever a property of the object changes, the setter function is triggered to notify the subscriber. This setter function is our listener:

// The observe method traverses and wraps object properties

function observe(target{

    // If target is an object, iterate over it

    if(target && typeof target === 'object') {

        Object.keys(target).forEach((key) = > {

            // The defineReactive method puts a "listener" on the target attribute

            defineReactive(target, key, target[key])

        })

    }

}

// Define defineReactive method

function defineReactive(target, key, val{

    // The property value may also be of type object, in which case you need to call observe for recursive traversal

    observe(val)

    // Install a listener for the current property

    Object.defineProperty(target, key, {

         / / can be enumerated

        enumerable: true.

        // Cannot be configured

        configurable: false.

        getfunction ({

            return val;

        },

        // The listener function

        set: function (value) {

            console.log(`${target}Properties of the${key}Properties from${val}The value becomes${value}`)

            val = value

        }

    });

}

Copy the code

Implement subscriber Dep as follows:

// Define the subscriber class Dep

class Dep {

    constructor() {

        // Initialize the subscription queue

        this.subs = []

    }

    

    // Add subscribers

    addSub(sub) {

        this.subs.push(sub)

    }

    

    // Notify subscribers (does all the code look familiar?)

    notify() {

        this.subs.forEach((sub) = >{

            sub.update()

        })

    }

}

Copy the code

Now we can override setter methods in defineReactive to notify subscribers in listeners:

function defineReactive(target, key, val{

    const dep = new Dep()

    // Listen for the current property

    observe(val)

    Object.defineProperty(target, key, {

        set(value) = > {

            // Notify all subscribers

            dep.notify()

        }

    })

}

Copy the code

Five, the summary

The observer pattern is also called the publish-subscribe pattern, model-view pattern, source-listener pattern, or subordinate pattern. It is an “object behavior pattern”. It defines a “one-to-many dependency between objects”, which notifies all observer objects when the state of the observed object changes and causes them to update automatically.

In real business, if the behavior of one object is “dependent” on the state of another object. In other words, when the state of the “target object” changes, it will directly affect the behavior of the “observer”. Try to consider using the observer mode to achieve this.

Six, develop

The observer model is similar to the publish-subscribe model, but there are big differences. Such as:

  • Coupling difference: The observer mode is more coupled than the publish-subscribe mode;
  • Different concerns: The observer model needs to be aware of each other’s existence, whereas the publishable/subscriptive model uses a dispatch center to contact publishers/subscribers.

See you in the next article.

Refer to the article

3. “Observer Pattern” 2. “Observer Pattern of TypeScript Design Pattern” 3.