前言
大家好,我是南木元元,熱衷分享有趣實(shí)用的文章。今天來(lái)聊聊設(shè)計(jì)模式中常用的觀察者模式和發(fā)布-訂閱模式。
觀察者模式
概念
觀察者模式定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都將得到通知。
如何理解這句話呢?來(lái)舉個(gè)生活中的例子
學(xué)生小明情緒比較容易波動(dòng),所以當(dāng)小明的情緒發(fā)生變化時(shí),父母和老師希望及時(shí)獲得通知,以便可以采取適當(dāng)?shù)拇胧﹣?lái)幫助他。
- 首先家長(zhǎng)和老師(觀察者)都會(huì)告訴小明他們對(duì)他的情緒狀態(tài)很關(guān)注。(訂閱事件)
- 當(dāng)小明(被觀察者)的情緒發(fā)生變化時(shí),他會(huì)通知所有注冊(cè)過(guò)的觀察者。例如,如果小明感到很開(kāi)心,他會(huì)告訴父母和老師:“我今天心情很好!”;如果他感到沮喪,他也會(huì)告訴父母和老師:“我今天感覺(jué)不太好?!保ㄍㄖ兓?/li>
這樣父母和老師就能及時(shí)了解小明的情緒狀態(tài),當(dāng)小明情緒低落時(shí),他們可以給予他關(guān)心、安慰和支持。
在這個(gè)例子中,小明就是被觀察者,而父母和老師都是觀察者。
代碼實(shí)現(xiàn)
觀察者模式有何應(yīng)用呢?
Vue的響應(yīng)式就是基于觀察者模式的,下面就來(lái)簡(jiǎn)單實(shí)現(xiàn)一下它的代碼。
// 被觀察者 學(xué)生
class Subject {
constructor() {
this.state = "happy";
this.observers = []; // 存儲(chǔ)所有的觀察者
}
//新增觀察者
add(o) {
this.observers.push(o);
}
//獲取狀態(tài)
getState() {
return this.state;
}
// 更新?tīng)顟B(tài)并通知
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(`親愛(ài)的${this.name} 通知您當(dāng)前學(xué)生的狀態(tài)是${student.getState()}`);
}
}
let student = new Subject();
let parent = new Observer("父母");
let teacher = new Observer("老師");
//添加觀察者
student.add(parent);
student.add(teacher);
//設(shè)置被觀察者的狀態(tài)
student.setState("sad");
輸出結(jié)果:
發(fā)布-訂閱模式
概念
發(fā)布訂閱模式跟觀察者模式很像,它們其實(shí)都有發(fā)布者和訂閱者,但是他們是有區(qū)別的:
- 觀察者模式的發(fā)布和訂閱是互相依賴的
- 發(fā)布訂閱模式的發(fā)布和訂閱是不互相依賴的,因?yàn)橛幸粋€(gè)統(tǒng)一調(diào)度中心
為了更好區(qū)分這兩種設(shè)計(jì)模式,接著上述例子。
- 所有老師都希望訂閱小明的情緒狀態(tài),他們向情緒監(jiān)測(cè)系統(tǒng)注冊(cè)自己,來(lái)時(shí)刻關(guān)注小明的情緒。(向調(diào)度中心訂閱事件)
- 當(dāng)小明的情緒發(fā)生變化時(shí),情緒監(jiān)測(cè)系統(tǒng)會(huì)將消息發(fā)布給所有訂閱了小明情緒狀態(tài)的老師。例如,如果小明在上課時(shí)感到煩躁,情緒監(jiān)測(cè)系統(tǒng)會(huì)發(fā)布消息給老師:“小明情緒不穩(wěn)定,請(qǐng)關(guān)注他的情緒變化。”(調(diào)度中心通知變化)
通過(guò)發(fā)布訂閱模式,小明不需要直接告訴每位老師他的情緒狀態(tài),而是通過(guò)情緒監(jiān)測(cè)系統(tǒng)自動(dòng)發(fā)布消息給所有訂閱了他情緒狀態(tài)的老師。這種發(fā)布者不直接接觸到訂閱者的模式,就是發(fā)布訂閱模式。
那么發(fā)布訂閱模式有何應(yīng)用呢?
Vue的EventBus事件總線其實(shí)就是用了發(fā)布訂閱模式。用法如下:
1.創(chuàng)建全局事件總線
// main.js
import Vue from "vue"
Vue.prototype.$bus = new Vue()
2.通過(guò)on訂閱事件
//組件A
export default{
mounted(){
// 監(jiān)聽(tīng)事件的觸發(fā)
this.$bus.$on("sendMsg", data => {
console.log(data)//身體健康
})
},
beforeDestroy(){
// 取消監(jiān)聽(tīng)
this.$bus.$off("sendMsg")
}
}
3.通過(guò)emit發(fā)布事件
//組件B
<template>
<button @click="handlerClick">點(diǎn)擊發(fā)送數(shù)據(jù)</button>
</template>
export default{
methods:{
handlerClick(){
this.$bus.$emit("sendMsg", "身體健康")
}
}
}
了解了EventBus的使用后,那么接下來(lái)就來(lái)手動(dòng)實(shí)現(xiàn)一個(gè)EventBus。
代碼實(shí)現(xiàn)
基礎(chǔ)版
實(shí)現(xiàn)目標(biāo):使用 $on 訂閱事件,使用 $emit 發(fā)布事件。
主要思路:
- 創(chuàng)建一個(gè)緩存列表對(duì)象,存放訂閱的事件名和回調(diào)
- on 方法用來(lái)把回調(diào)函數(shù)都加到緩存列表中(訂閱者注冊(cè)事件到調(diào)度中心)
- emit方法根據(jù)事件名去逐個(gè)執(zhí)行對(duì)應(yīng)緩存列表中的函數(shù)(發(fā)布者發(fā)布事件到調(diào)度中心)
class EventBus {
constructor() {
// 緩存列表,用來(lái)存放注冊(cè)的事件與回調(diào)
this.cache = {};
}
// 訂閱事件
on(name, cb) {
// 如果當(dāng)前事件沒(méi)有訂閱過(guò),就給事件創(chuàng)建一個(gè)隊(duì)列
if (!this.cache[name]) {
this.cache[name] = []; //由于一個(gè)事件可能注冊(cè)多個(gè)回調(diào)函數(shù),所以使用數(shù)組來(lái)存儲(chǔ)事件隊(duì)列
}
this.cache[name].push(cb);
}
// 觸發(fā)事件
emit(name, ...args) {
// 檢查目標(biāo)事件是否有監(jiān)聽(tīng)函數(shù)隊(duì)列
if (this.cache[name]) {
// 逐個(gè)調(diào)用隊(duì)列里的回調(diào)函數(shù)
this.cache[name].forEach((callback) => {
callback(...args);
});
}
}
}
// 測(cè)試
let eventBus = new EventBus();
// 訂閱事件
eventBus.on("teacherName1", (pos, state) => {
console.log(`訂閱者小陳老師,小明同學(xué)當(dāng)前在${pos},心情狀態(tài)是${state}`);
});
eventBus.on("teacherName1", (pos, state) => {
console.log(`訂閱者小陳老師,小明同學(xué)當(dāng)前在${pos},心情狀態(tài)是${state}`);
});
eventBus.on("teacherName2", (pos, state) => {
console.log(`訂閱者小李老師,小明同學(xué)當(dāng)前在${pos},心情狀態(tài)是${state}`);
});
// 發(fā)布事件
eventBus.emit("teacherName1", "教室", "傷心");
eventBus.emit("teacherName2", "操場(chǎng)", "開(kāi)心");
輸出結(jié)果:
取消訂閱
實(shí)現(xiàn)目標(biāo):增加 off 方法取消訂閱。
- off 方法:找到當(dāng)前取消事件名對(duì)應(yīng)的函數(shù)隊(duì)列中相應(yīng)回調(diào),進(jìn)行刪除
class EventBus {
constructor() {
// 緩存列表,用來(lái)存放注冊(cè)的事件與回調(diào)
this.cache = {};
}
// 訂閱事件
on(name, cb) {
// 如果當(dāng)前事件沒(méi)有訂閱過(guò),就給事件創(chuàng)建一個(gè)隊(duì)列
if (!this.cache[name]) {
this.cache[name] = []; //由于一個(gè)事件可能注冊(cè)多個(gè)回調(diào)函數(shù),所以使用數(shù)組來(lái)存儲(chǔ)事件隊(duì)列
}
this.cache[name].push(cb);
}
// 觸發(fā)事件
emit(name, ...args) {
// 檢查目標(biāo)事件是否有監(jiān)聽(tīng)函數(shù)隊(duì)列
if (this.cache[name]) {
// 逐個(gè)調(diào)用隊(duì)列里的回調(diào)函數(shù)
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);
}
}
}
// 測(cè)試
let eventBus = new EventBus();
let event1 = function (...args) {
console.log(`通知1-訂閱者小陳老師,小明同學(xué)當(dāng)前心情狀態(tài):${args}`)
};
let event2 = function (...args) {
console.log(`通知2-訂閱者小陳老師,小明同學(xué)當(dāng)前心情狀態(tài):${args}`)
};
// 訂閱事件
eventBus.on("teacherName1", event1);
eventBus.on("teacherName1", event2);
// 取消訂閱事件1
eventBus.off('teacherName1', event1);
// 發(fā)布事件
eventBus.emit("teacherName1", "教室", "上課", "打架", "憤怒");
eventBus.emit("teacherName2", "教室", "上課", "打架", "憤怒");
輸出結(jié)果:
訂閱一次
實(shí)現(xiàn)目標(biāo):增加 once 方法只訂閱一次。
- once 方法只監(jiān)聽(tīng)一次,執(zhí)行完第一次回調(diào)函數(shù)后,自動(dòng)刪除當(dāng)前訂閱事件
class EventBus {
constructor() {
// 緩存列表,用來(lái)存放注冊(cè)的事件與回調(diào)
this.cache = {};
}
// 訂閱事件
on(name, cb) {
// 如果當(dāng)前事件沒(méi)有訂閱過(guò),就給事件創(chuàng)建一個(gè)隊(duì)列
if (!this.cache[name]) {
this.cache[name] = []; //由于一個(gè)事件可能注冊(cè)多個(gè)回調(diào)函數(shù),所以使用數(shù)組來(lái)存儲(chǔ)事件隊(duì)列
}
this.cache[name].push(cb);
}
// 觸發(fā)事件
emit(name, ...args) {
// 檢查目標(biāo)事件是否有監(jiān)聽(tīng)函數(shù)隊(duì)列
if (this.cache[name]) {
// 逐個(gè)調(diào)用隊(duì)列里的回調(diào)函數(shù)
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) {
// 執(zhí)行完第一次回調(diào)函數(shù)后,自動(dòng)刪除當(dāng)前訂閱事件
const fn = (...args) => {
cb(...args);
this.off(name, fn);
};
this.on(name, fn);
}
}
// 測(cè)試
let eventBus = new EventBus();
let event1 = function (...args) {
console.log(`通知1-訂閱者小陳老師,小明同學(xué)當(dāng)前心情狀態(tài):${args}`)
};
// 訂閱事件,只訂閱一次
eventBus.once("teacherName1", event1);
// 發(fā)布事件
eventBus.emit("teacherName1", "教室", "上課", "打架", "憤怒");
eventBus.emit("teacherName1", "教室", "上課", "打架", "憤怒");
eventBus.emit("teacherName1", "教室", "上課", "打架", "憤怒");
輸出結(jié)果:
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-661793.html
結(jié)語(yǔ)
??如果此文對(duì)你有幫助的話,歡迎??關(guān)注、??點(diǎn)贊、?收藏、??評(píng)論,支持一下博主~文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-661793.html
到了這里,關(guān)于JavaScript 簡(jiǎn)單實(shí)現(xiàn)觀察者模式和發(fā)布-訂閱模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!