JavaScript 設計模式 --- 發佈訂閱模式

前言

對於前端同學而言,發佈-訂閱模式應該是最熟悉的設計模式了。平常寫的最多的事件監聽就是一個最好的例子。

var dom = document.querySelector('xxx');
dom.addEventListener('click', () => { //do something });
dom.click();

這裏開發同學訂閱這個dom對象的click事件,並且傳入了一個回調函數。當這個事件被觸發(用戶點擊),就會執行這個回調。當然,我們可以也可以訂閱這個對象的其他事件,也可以多次訂閱同一個事件並且傳入不同的回調。

使用場景

這個設計模式的優點十分明顯,他可以解耦發佈者和訂閱者,看一段業務中經常用到的場景。
假設一個頁面需要一個接口來渲染三個模塊。分別爲A。B。C。當接口返回時,要調用對應模塊的業務邏輯接口。

import { A, B, C } from 'dir';
getApi().then((data) => {
	A.renderA();
	B.renderB();
	C.renderC();
});

這裏有幾個問題

  1. 假如這個getApi是你負責的模塊,而A B C模塊都是分別由其他人維護的,你需要侵入到模塊內部,來調用他們的方法。
  2. 如果業務邏輯發生變化,增加一個D模塊,那麼此時你需要在回調裏面再新增D模塊,再新增的話你還得再加。其實這是不合理的,不能因爲有其他模塊的介入我們就得修改一次代碼,這樣的強耦合會造成業務邏輯的不可維護。

這個時候就可以利用一下發布-訂閱模式來解決這個問題。

我們修改一下上述代碼

// event.js
class Event {
    constructor(){
        this.watchList = {};
    }

    listen(key,fn){
        if (this.watchList[key]) {
            this.watchList[key].push(fn);
        } else {
            this.watchList[key] = [fn];
        }
        
    }

    trigger(...args){
        const key = args.shift();
        if (!key) {
            return;
        }
        const fns = this.watchList[key];
        if (!fns) {
            return;
        }
        
        for(let i=0;i<fns.length;i++) {
            fns[i] && fns[i].apply(this,args);
        }
    }
}
export default new Event();

// A.js
import event from event

const A = {
	renderA:() => { // do something }
};

event.listen('data-ready',() => {
	A.renderA();
})


// B.js 和 C.js 同理


// page.js
import event from 'event';
getApi().then((data) => {
	event.trigger('data-ready',data);
});

這麼一來,不管是A B C 模塊的內部方法發生了改變,還是要新增其他的模塊,請求API後的回調邏輯就不用再改動了。其他模塊的人只要維護好他們自己的方法就行。

寫在最後

除了解耦模塊和模塊之間的關係以外,這個設計模式在其他領域也常被用到,比如消息中間件的異步通信中,上游不用關心下游實時返回的結果,而是等結果產生了再調用對應的事件回調。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章