preface

Most of the time, objects do not exist independently, and a change in the behavior of one object may cause a change in the behavior of one or more other objects.

For example, in real life, the vehicle stops at a red light and goes at a green light; Another example is the model-view relationship in MVC or MVVM patterns. Associating these dependent objects and triggering corresponding behaviors is the application scenario of the observer pattern.

1. What is the observer model

1.1 an 🌰

Let’s start by looking at two common examples to get a feel for the observer model.

DOM events

Often we need to do something when the user clicks on a DOM element, but we can’t predict when the user will click.

Binding event functions to DOM nodes is an implementation of the observer pattern:

var dom = document.getElementById('box');
dom.addEventListener('click'.function () {
  console.log("click box");
}, false);

// Simulate clicking
dom.click();  // click box
Copy the code

The above example listens for the DOM click event, which triggers the execution of the callback function when the DOM is clicked.

In this case, the DOM click event is the observed, and the listening event is the observer.

Wechat public account article push

The process of subscribing to wechat public account and receiving article push is also a typical example of application observer mode, which can be simply implemented:

// Observed: wechat official account
class WxBar {
  constructor(topic) {
    this.topic = topic;
    this.observers = new Set(a);// Cache list
  }
  addObserver(ob) {
    this.observers.add(ob);
  }
  removeObserver(ob) {
    this.observers.delete(ob);
  }
  // Push the article
  pushArticle(article) {
    console.log('push article');
    for (let ob of this.observers) {  // Iterate over the cache list
      ob.update(this.topic, article)  // Trigger the observer callback function}}}// Observer: wechat user
class WxUser {
  constructor(name) {
    this.name = name;
  }
  update(topic, article) {
    console.log(this.name + ' receive article from ' + topic)
  }
}

const wxBar = new WxBar("JS DP");
const livia = new WxUser('Livia');
const cindy = new WxUser('Cindy');
wxBar.addObserver(livia);
wxBar.addObserver(cindy);
wxBar.pushArticle({});
// push article
// Livia receive article from JS DP
// Cindy receive article from JS DP

// Unsubscribe from the public account
wxBar.removeObserver(cindy);
wxBar.pushArticle({});
// push article
// Livia receive article from JS DP
Copy the code

The above process can be roughly divided into the following points:

  • The first step is to specify the observed, namely wechat public account
  • Then add a cache list to the observed to store callback functions to notify the observer, namely wechat users
  • When an article is pushed, the observed iterates through the cache list, triggering the observer callbacks stored in it
  • When unsubscribing, the observed removes the observer from the cache list

1.2 Definition and Features

The observer pattern is a behavioral design pattern that defines a one-to-many dependency between objects. When an object’s state changes, all dependent objects are notified and automatically updated.

As can be seen from the above examples, the observer mode reduces the coupling relationship between the target and the observer, and establishes a trigger mechanism between the target and the observer. However, the dependency between target and observer is not completely removed, and circular references may occur.

2. Observer vs. Publish/subscribe

Most documents document that the observer pattern is the same as the publish-subscribe pattern, arguing that publish-subscribe is another name for the observer pattern.

However, the observer pattern is target-oriented and watch-oriented programming, while the publish-subscribe pattern is scheduler-centric programming.

After joining the dispatching center, the above example of article push by wechat public account can be simply rewritten as:

// The dispatch center
class PubSub {
  constructor() {
    this.subscribers = new Set(a);// Cache list
  }
  subscribe(ob) {
    this.subscribers.add(ob);
  }
  unsubscribe(ob) {
    this.subscribers.delete(ob);
  }
  publish(topic, params) {
    for (let ob of this.subscribers) {  // Iterate over the cache list
      ob.update(topic, params)
    }
  }
}
// Publisher: wechat official account
class WxBar {
  constructor(topic) {
    this.topic = topic;
  }
  // Push the article
  pushArticle(pubSub, article) {
    console.log('push article');
    pubSub.publish(this.topic, article)
  }
}
// Subscriber: wechat user
class WxUser {
  constructor(name) {
    this.name = name;
  }
  update(topic, article) {
    console.log(this.name + ' receive article from ' + topic)
  }
}

let pubSub = new PubSub();

const wxBar = new WxBar("JS DP");
const livia = new WxUser('Livia');
const cindy = new WxUser('Cindy');

pubSub.subscribe(livia);
pubSub.subscribe(cindy);
wxBar.pushArticle(pubSub, {});
Copy the code

In the above example, there is an additional message queue or PubSub role in the publish subscribe mode.

With the addition of this role, publishers and subscribers do not need to know each other, they just communicate with the help of the dispatch center. There is no direct connection between publisher and subscriber at all, enabling understanding coupling.

The observer pattern is implemented primarily in a synchronous manner, where the principal invokes the appropriate methods of all its observers when some event occurs. The publisher/subscriber pattern is primarily implemented asynchronously through the use of message queues.

3. Application example — Vue responsive principle

When it comes to observer mode, it is inevitable to talk about the responsive principle of Vue. The following is a simple implementation of an object property change, i.e. update view:

class Dep {
  constructor() {
    // List of observers
    this.subs = [];
  }

  addSub(sub) {
    this.subs.push(sub);
  }

  notify() {
    this.subs.forEach((sub) = >{ sub.update(); }}})/ / observer
class Watcher {
  constructor() {
    // Set dep. target to the current observer
    Dep.target = this;
  }

  update() {
    // do something
    console.log("re render view"); }}function defineReactive(obj, key, val) {
  const dep = new Dep();
  Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: function () {
      // dep. target points to the observer and collects the watcher
      Dep.target && dep.addSub(Dep.target);
      return val;
    },
    set: function (newVal) {
      // The new value is equal to the old value, or the new value and the old value are NaN
      if(newVal === val || (newVal ! == newVal && value ! == value))return; val = newVal; dep.notify(); }}); }// Make the object observable (for now, only the first level of object properties)
function observer(value) {
  if(! value || (typeofvalue ! = ='object')) {
    return;
  }
  Object.keys(value).forEach((key) = > {
    defineReactive(value, key, value[key]);
  });
}

// A simple Vue constructor
class Vue {
  constructor(options) {
    this._data = options.data;
    observer(this._data);
    new Watcher();
    console.log('render:'.this._data.test);  // Trigger get > dep.addSub(dep.target)}}const o = new Vue({
  data: {
    test: "init test."}}); o._data.test ="Hello world.";  // Set > dep.notify()

Dep.target = null;
Copy the code

Execution steps:

  • Firstly, the observed is identified, i.eVuethedataObjects, or pairsdataListening for property changes.
  • throughdefineReactive > Object.definePropertymakedataThe properties of the object become observable.
  • Create an observer that willDep.targetPointing to the current observer, printingthis._data.testValue adds the observer to the cache list.
  • To change theo._data.testTriggers the objectsetProperty to listen throughdep.notify()Notify the observer of the update.

Where dep. target is an attribute of class Dep, not instance Dep, and can be accessed globally and changed arbitrarily. By pointing dep. target to the observer before the subscription and setting the subscription to null, you have the flexibility to add the observer to the Dep’s cache list only when the relevant attribute is applied.

conclusion

The essence of the observer mode is to trigger linkage. When the state of the target object is modified, the corresponding notification is triggered, and the changes are linked to the observer by traversing the cache list of the observer object.

Formally, the publish-subscribe pattern has one more dispatch center than the observer pattern. However, both of them are intended to achieve one-to-many dependency between objects, and the automatic update of dependent objects is triggered by the state change of the subject.