從觀察者模式到手寫EventEmitter源碼

觀察者模式

觀察者模式(observer)普遍的應用於javascript語言中,瀏覽器事件(如鼠標單擊click,鍵盤事件keyDown)都是該模式的例子。設計這種模式背後的主要緣由是促進造成低耦合,在這種模式中不是簡單的對象調用對象,而是一個對象「訂閱」另外一個對象的某個活動,當對象的活動狀態發生了改變,就去通知訂閱者,而訂閱者也稱爲觀察者。javascript

報紙訂閱

生活中就像是去報社訂報紙,你喜歡讀什麼報就去報社去交錢訂閱,當發佈了新報紙的時候,報社會向全部訂閱了報紙的每個人發送一份,訂閱者就能夠接收到。java

咱們能夠利用這個例子來使用javascript來模擬一下。假設有一個發佈者Jack,它天天出版報紙雜誌,訂閱者Tom將被通知任什麼時候候發生的新聞。node

Jack要有一個subscribers屬性,它是一個數組類型,訂閱的行爲將會按順序存放在這個數組中,而通知意味着調用訂閱者對象的某個方法。所以,當用戶Tom訂閱信息的時候,該訂閱者要向Jack的subscribe()提供他的一個方法。固然也能夠退訂,我不想再看報紙了,就調用unsubscribe()取消訂閱。數組

一個簡單的觀察者模式應有如下成員:瀏覽器

  • subscribes 一個數組
  • subscribe() 將訂閱添加到數組裏
  • unsubscribe() 把訂閱從數組中移除
  • publish() 迭代數組,調用訂閱時的方法

這個模式中還須要一個type參數,用於區分訂閱的類型,若有的人訂閱的是娛樂新聞,有的人訂閱的是體育雜誌,使用此屬性來標記。函數

咱們使用簡單的代碼來實現它:this

var Jack = {
    subscribers: {
        'any': []
    },
    //添加訂閱
    subscribe: function (type = 'any', fn) {
        if (!this.subscribers[type]) {
            this.subscribers[type] = [];
        }
        this.subscribers[type].push(fn); //將訂閱方法保存在數組裏
    },
    //退訂
    unsubscribe: function (type = 'any', fn) {
        this.subscribers[type] =
            this.subscribers[type].filter(function (item) { 
                return item !== fn;
            }); //將退訂的方法從數組中移除
    },
    //發佈訂閱
    publish: function (type = 'any', ...args) {
        this.subscribers[type].forEach(function (item) { 
            item(...args);    //根據不一樣的類型調用相應的方法
        });
    }
};

以上就是一個最簡單的觀察者模式的實現,能夠看到代碼很是的簡單,核心原理就是將訂閱的方法按分類存在一個數組中,當發佈時取出執行便可。spa

下面使用Tom來訂報:prototype

var Tom = {
    readNews: function (info) {
        console.log(info);
    }
};

//Tom訂閱Jack的報紙
Jack.subscribe('娛樂', Tom.readNews);
Jack.subscribe('體育', Tom.readNews);

//Tom 退訂娛樂新聞:
Jack.unsubscribe('娛樂', Tom.readNews);

//發佈新報紙:
Jack.publish('娛樂', 'S.H.E演唱會驚喜登臺')
Jack.publish('體育', '歐國聯-意大利0-1客負葡萄牙');

運行結果:設計

歐國聯-意大利0-1客負葡萄牙

觀察者模式的實際應用

能夠看到觀察者模式將兩個對象的關係變得十分鬆散,當不須要訂閱關係的時候刪掉訂閱的語句便可。那麼在實際應用中有哪些地方使用了這個模式呢?

events模塊

node.js的events是一個使用率很高的模塊,其它原生node.js模塊都是基於它來完成的,好比流、HTTP等,咱們能夠手寫一版events的核心代碼,看看觀察者模式的實際應用。

events模塊的功能就是一個事件綁定,全部繼承自它的實例都具有事件處理的能力。首先它是一個類,咱們寫出它的基本結構:

function EventEmitter() {
    //私有屬性,保存訂閱方法
    this._events = {};
}

//默認最大監聽數
EventEmitter.defaultMaxListeners = 10;

module.exports = EventEmitter;

下面咱們一個個將events的核心方法實現。

on方法

首先是on方法,該方法用於訂閱事件,在舊版本的node.js中是addListener方法,它們是同一個函數:

EventEmitter.prototype.on =
    EventEmitter.prototype.addListener = function (type, listener, flag) {
        //保證存在實例屬性
        if (!this._events) this._events = Object.create(null);

        if (this._events[type]) {
            if (flag) {//從頭部插入
                this._events[type].unshift(listener);
            } else {
                this._events[type].push(listener);
            }

        } else {
            this._events[type] = [listener];
        }
        //綁定事件,觸發newListener
        if (type !== 'newListener') {
            this.emit('newListener', type);
        }
    };

由於有其它子類須要繼承自EventEmitter,所以要判斷子類是否存在_event屬性,這樣作是爲了保證子類必須存在此實例屬性。而flag標記是一個訂閱方法的插入標識,若是爲'true'就視爲插入在數組的頭部。能夠看到,這就是觀察者模式的訂閱方法實現。

emit方法

EventEmitter.prototype.emit = function (type, ...args) {
    if (this._events[type]) {
        this._events[type].forEach(fn => fn.call(this, ...args));
    }
};

emit方法就是將訂閱方法取出執行,使用call方法來修正this的指向,使其指向子類的實例。

once方法

EventEmitter.prototype.once = function (type, listener) {
    let _this = this;

    //中間函數,在調用完以後當即刪除訂閱
    function only() {
        listener();
        _this.removeListener(type, only);
    }
    //origin保存原回調的引用,用於remove時的判斷
    only.origin = listener;
    this.on(type, only);
};

once方法很是有趣,它的功能是將事件訂閱「一次」,當這個事件觸發過就不會再次觸發了。其原理是將訂閱的方法再包裹一層函數,在執行後將此函數移除便可。

off方法

EventEmitter.prototype.off =
    EventEmitter.prototype.removeListener = function (type, listener) {

        if (this._events[type]) {
        //過濾掉退訂的方法,從數組中移除
            this._events[type] =
                this._events[type].filter(fn => {
                    return fn !== listener && fn.origin !== listener
                });
        }
    };

off方法即爲退訂,原理同觀察者模式同樣,將訂閱方法從數組中移除便可。

prependListener方法

EventEmitter.prototype.prependListener = function (type, listener) {
    this.on(type, listener, true);
};

此方法沒必要多說了,調用on方法將標記傳爲true(插入訂閱方法在頭部)便可。

以上,就將EventEmitter類的核心方法實現了。

小結

經過建立「可觀察的」對象,當發生一個感興趣的事件時可將該事件通告給全部觀察者,從而造成鬆散的耦合。

部分實例參考《JavaScript模式》做者:Stoyan Stefanov
相關文章
相關標籤/搜索