JavaScript simply implements observer mode and publish-subscribe mode

1. Observer pattern

1.1 What is the observer pattern

Concept: The observer pattern defines a one-to-many dependency relationship between objects. When the state of an object changes, all objects that depend on it will be notified.

How to understand this sentence? Let's give an example from real life

Student Xiao Ming's mood is easy to fluctuate, so when Xiao Ming's mood changes, parents and teachers hope to be notified in time so that they can take appropriate measures to help him.

  • First of all, parents and teachers (observers) will tell Xiao Ming that they are very concerned about his emotional state. (subscribe to events)
  • When Xiao Ming (observed)'s mood changes, he will notify all registered observers. For example, if Xiao Ming is feeling happy, he will tell his parents and teachers: "I am in a good mood today!"; if he is depressed, he will also tell his parents and teachers: "I am not feeling very well today." (notification changes)

In this way, parents and teachers can keep abreast of Xiaoming's emotional state, and when Xiaoming is depressed, they can give him care, comfort and support.
In this example, Xiao Ming is the observed person, while the parents and teachers are the observers.

1.2 Code implementation

Here's a simple implementation of its code.

class Subject {
    
    
  // 被观察者 学生
  constructor() {
    
    
    this.state = "happy";
    this.observers = []; // 存储所有的观察者
  }
  //新增观察者
  add(o) {
    
    
    this.observers.push(o);
  }
  // 更新状态
  setState(newState) {
    
    
    // 更新状态后通知
    this.state = newState;
    this.notify();
  }
  //通知所有的观察者
  notify() {
    
    
    this.observers.forEach((o) => o.update(this));
  }
}

class Observer {
    
    
  // 观察者 父母和老师
  constructor(name) {
    
    
    this.name = name;
  }
  //通知更新
  update(student) {
    
    
    console.log(`亲爱的${
      
      this.name} 通知您当前学生的状态是${
      
      student.state}`);
  }
}

//创建被观察者学生
let student = new Subject("学生");
//创建观察者父母和老师
let parent = new Observer("父母");
let teacher = new Observer("老师");
//给被观察者学生增加观察者
student.add(parent);
student.add(teacher);

student.setState("sad");
//亲爱的父母 通知您当前学生的状态是sad
//亲爱的老师 通知您当前学生的状态是sad

2. Publish-subscribe model

2.1 What is the publish-subscribe model

The publish-subscribe mode is very similar to the observer mode. They actually have publishers and subscribers , but they are different:

  • Publish and subscribe in the observer mode are interdependent
  • The publication and subscription of the publish-subscribe mode are not dependent on each other, because there is a unified dispatch center

In order to better distinguish these two design patterns, follow the above example.

  • All teachers want to subscribe to Xiaoming's emotional state, and they register themselves with the emotion monitoring system to keep an eye on Xiaoming's emotions. (Subscribe events to dispatch center)
  • When Xiao Ming's mood changes, the mood monitoring system will publish a message to all teachers who have subscribed to Xiao Ming's emotional state. For example, if Xiao Ming feels irritable in class, the emotional monitoring system will send a message to the teacher: "Xiao Ming is emotionally unstable, please pay attention to his emotional changes." (dispatch center notifies changes)

Through the publish-subscribe model, Xiao Ming does not need to directly tell each teacher his emotional state, but automatically publishes a message to all teachers who have subscribed to his emotional state through the emotional monitoring system. This mode in which the publisher does not directly contact the subscriber is the publish-subscribe mode.
insert image description here
So what is the application of the publish-subscribe model?
Vue's EventBus event bus actually uses the publish-subscribe model. The usage is as follows:
1. Create a global event bus

// main.js
import Vue from "vue"
Vue.prototype.$bus = new Vue()

2. Subscribe to events through on

//组件A
export default{
    
    
    mounted(){
    
    
        // 监听事件的触发
        this.$bus.$on("sendMsg", data => {
    
    
            console.log(data)//身体健康
        })
    },
    beforeDestroy(){
    
    
        // 取消监听
        this.$bus.$off("sendMsg")
    }
}

3. Publish events through emit

//组件B
<template>
    <button @click="handlerClick">点击发送数据</button>
</template>
export default{
    
    
    methods:{
    
    
        handlerClick(){
    
    
            this.$bus.$emit("sendMsg", "身体健康")
        }
    }
}

After understanding the use of EventBus, the next step is to manually implement an EventBus.

2.2 Code implementation

2.2.1 Basic version

To achieve the goal: use $on to subscribe to events, and use $emit to publish events.
Main ideas:

  • Create a cache list object to store the subscribed event name and callback
  • The on method is used to add callback functions to the cache list (subscribers register events to the dispatch center)
  • The emit method executes the functions in the corresponding cache list one by one according to the event name (the publisher publishes the event to the dispatch center)
class EventBus {
    
    
  constructor() {
    
    
    // 缓存列表,用来存放注册的事件与回调
    this.cache = {
    
    };
  }

  // 订阅事件
  on(name, cb) {
    
    
    // 如果当前事件没有订阅过,就给事件创建一个队列
    if (!this.cache[name]) {
    
    
      this.cache[name] = []; //由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
    }
    this.cache[name].push(cb); 
  }

  // 触发事件
  emit(name, ...args) {
    
    
    // 检查目标事件是否有监听函数队列
    if (this.cache[name]) {
    
    
      // 逐个调用队列里的回调函数
      this.cache[name].forEach((callback) => {
    
    
        callback(...args);
      });
    }
  }
}

// 测试
let eventBus = new EventBus();
// 订阅事件
eventBus.on("teacherName1", (pos, state) => {
    
    
  console.log(`订阅者小陈老师,小明同学当前在${
      
      pos},心情状态是${
      
      state}`);
});
eventBus.on("teacherName1", (pos, state) => {
    
    
  console.log(`订阅者小陈老师,小明同学当前在${
      
      pos},心情状态是${
      
      state}`);
});
eventBus.on("teacherName2", (pos, state) => {
    
    
  console.log(`订阅者小李老师,小明同学当前在${
      
      pos},心情状态是${
      
      state}`);
});
// 发布事件
eventBus.emit("teacherName1", "教室", "伤心");
eventBus.emit("teacherName2", "操场", "开心");

Output result:

insert image description here

2.2.2 Unsubscribe

Realize the goal: increase the off method to cancel the subscription.

  • off method: find the corresponding callback in the function queue corresponding to the current cancellation event name, and delete it
class EventBus {
    
    
  constructor() {
    
    
    // 缓存列表,用来存放注册的事件与回调
    this.cache = {
    
    };
  }

  // 订阅事件
  on(name, cb) {
    
    
    // 如果当前事件没有订阅过,就给事件创建一个队列
    if (!this.cache[name]) {
    
    
      this.cache[name] = []; //由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
    }
    this.cache[name].push(cb); 
  }

  // 触发事件
  emit(name, ...args) {
    
    
    // 检查目标事件是否有监听函数队列
    if (this.cache[name]) {
    
    
      // 逐个调用队列里的回调函数
      this.cache[name].forEach((callback) => {
    
    
        callback(...args);
      });
    }
  }

  // 取消订阅
  off(name, cb) {
    
    
    const callbacks = this.cache[name]; 
    const index = callbacks.indexOf(cb); 
    if (index !== -1) {
    
    
      callbacks.splice(index, 1); 
    }
  }
}

// 测试
let eventBus = new EventBus();
let event1 = function (...args) {
    
    
  console.log(`通知1-订阅者小陈老师,小明同学当前心情状态:${
      
      args}`)
};
let event2 = function (...args) {
    
    
  console.log(`通知2-订阅者小陈老师,小明同学当前心情状态:${
      
      args}`)
};
// 订阅事件
eventBus.on("teacherName1", event1);
eventBus.on("teacherName1", event2);
// 取消订阅事件1
eventBus.off('teacherName1', event1);
// 发布事件
eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒");
eventBus.emit("teacherName2", "教室", "上课", "打架", "愤怒");

Output result:

insert image description here

2.2.3 Subscribe Once

Realize the goal: add the once method to subscribe only once.

  • The once method only listens once, and after the first callback function is executed, the current subscription event is automatically deleted
class EventBus {
    
    
  constructor() {
    
    
    // 缓存列表,用来存放注册的事件与回调
    this.cache = {
    
    };
  }

  // 订阅事件
  on(name, cb) {
    
    
    // 如果当前事件没有订阅过,就给事件创建一个队列
    if (!this.cache[name]) {
    
    
      this.cache[name] = []; //由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
    }
    this.cache[name].push(cb); 
  }

  // 触发事件
  emit(name, ...args) {
    
    
    // 检查目标事件是否有监听函数队列
    if (this.cache[name]) {
    
    
      // 逐个调用队列里的回调函数
      this.cache[name].forEach((callback) => {
    
    
        callback(...args);
      });
    }
  }

  // 取消订阅
  off(name, cb) {
    
    
    const callbacks = this.cache[name]; 
    const index = callbacks.indexOf(cb); 
    if (index !== -1) {
    
    
      callbacks.splice(index, 1); 
    }
  }

  // 只订阅一次
  once(name, cb) {
    
    
    // 执行完第一次回调函数后,自动删除当前订阅事件
    const wrapper = (...args) => {
    
    
      cb(args); 
      this.off(name, wrapper); 
    };
    this.on(name, wrapper);
  }
}

// 测试
let eventBus = new EventBus();
let event1 = function (...args) {
    
    
  console.log(`通知1-订阅者小陈老师,小明同学当前心情状态:${
      
      args}`)
};
// 订阅事件,只订阅一次
eventBus.once("teacherName1", event1);
// 发布事件
eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒");
eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒");
eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒");

Output result:

insert image description here

Writing is not easy, your praise and comment are the biggest motivation for me to move forward. If you have any questions, please point them out!

Guess you like

Origin blog.csdn.net/weixin_43288600/article/details/131968091