淺析JavaScript設(shè)計模式——發(fā)布-訂閱/觀察者模式

觀察者模式

定義對象間的一種一對多的依賴關(guān)系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都將得到通知

前一段時間一直在寫CSS3的文章

一直都沒寫設(shè)計模式

今天來寫寫大名鼎鼎觀察者模式

先畫張圖


觀察者模式的理解

我覺得還是發(fā)布-訂閱模式的叫法更容易我們理解

(不過也有的書上認為它們是兩種模式……)

這就類似我們在微信平臺訂閱了公眾號

當它有新的文章發(fā)表后,就會推送給我們所有訂閱的人

我們可以看到例子中這種模式的優(yōu)點

我們作為訂閱者不必每次都去查看這個公眾號有沒有新文章發(fā)布,

公眾號作為發(fā)布者會在合適時間通知我們

我們與公眾號之間不再強耦合在一起。公眾號不關(guān)心誰訂閱了它,

不管你是男是女還是寵物狗,它只需要定時向所有訂閱者發(fā)布消息即可

很簡單的道理,過年的時候,群發(fā)祝福短信一定要比挨個發(fā)短信方便的多

通過上面的例子映射出我們觀察者模式的優(yōu)點

可以廣泛應(yīng)用于異步編程,它可以代替我們傳統(tǒng)的回調(diào)函數(shù)

我們不需要關(guān)注對象在異步執(zhí)行階段的內(nèi)部狀態(tài),我們只關(guān)心事件完成的時間點

取代對象之間硬編碼通知機制,一個對象不必顯式調(diào)用另一個對象的接口,而是松耦合的聯(lián)系在一起

雖然不知道彼此的細節(jié),但不影響相互通信。更重要的是,其中一個對象改變不會影響另一個對象

可能看完這些一臉懵逼,不過沒關(guān)系

下面我們會深入體會它的優(yōu)點

自定義事件

其實觀察者模式我們都曾使用過,就是我們熟悉的事件

但是內(nèi)置的事件很多時候不能滿足我們的要求

所以我們需要自定義事件

現(xiàn)在我們想實現(xiàn)這樣的功能

定義一個事件對象,它有以下功能

監(jiān)聽事件(訂閱公眾號)

觸發(fā)事件(公眾號發(fā)布)

移除事件(取訂公眾號)

當然我們不可能只訂閱一個公眾號,可能會有很多

所以我們要針對不同的事件設(shè)置不同的”鍵”

這樣我們儲存事件的結(jié)構(gòu)應(yīng)該是這樣的

//偽代碼

Event = {

? ? name1: [回調(diào)函數(shù)1,回調(diào)函數(shù)2,...],

? ? name2: [回調(diào)函數(shù)1,回調(diào)函數(shù)2,...],

? ? name3: [回調(diào)函數(shù)1,回調(diào)函數(shù)2,...],

}

代碼如下

var Event = (function(){

? ? var list = {},

? ? ? ? listen,

? ? ? ? trigger,

? ? ? ? remove;

? ? listen = function(key,fn){ //監(jiān)聽事件函數(shù)

? ? ? ? if(!list[key]){

? ? ? ? ? ? list[key] = []; //如果事件列表中還沒有key值命名空間,創(chuàng)建

? ? ? ? }

? ? ? ? list[key].push(fn); //將回調(diào)函數(shù)推入對象的“鍵”對應(yīng)的“值”回調(diào)數(shù)組

? ? };

? ? trigger = function(){ //觸發(fā)事件函數(shù)

? ? ? ? var key = Array.prototype.shift.call(arguments); //第一個參數(shù)指定“鍵”

? ? ? ? msg = list[key];

? ? ? ? if(!msg || msg.length === 0){

? ? ? ? ? ? return false; //如果回調(diào)數(shù)組不存在或為空則返回false

? ? ? ? }

? ? ? ? for(var i = 0; i < msg.length; i++){

? ? ? ? ? ? msg[i].apply(this, arguments); //循環(huán)回調(diào)數(shù)組執(zhí)行回調(diào)函數(shù)

? ? ? ? }

? ? };

? ? remove = function(key, fn){ //移除事件函數(shù)

? ? ? ? var msg = list[key];

? ? ? ? if(!msg){

? ? ? ? ? ? return false; //事件不存在直接返回false

? ? ? ? }

? ? ? ? if(!fn){

? ? ? ? ? ? delete list[key]; //如果沒有后續(xù)參數(shù),則刪除整個回調(diào)數(shù)組

? ? ? ? }else{

? ? ? ? ? ? for(var i = 0; i < msg.length; i++){

? ? ? ? ? ? ? ? if(fn === msg[i]){

? ? ? ? ? ? ? ? ? ? msg.splice(i, 1); //刪除特定回調(diào)數(shù)組中的回調(diào)函數(shù)

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? };

? ? return {

? ? ? ? listen: listen,

? ? ? ? trigger: trigger,

? ? ? ? remove: remove

? ? }

})();

var fn = function(data){

? ? console.log(data + '的推送消息:xxxxxx......');

}

Event.listen('某公眾號', fn);

Event.trigger('某公眾號', '2016.11.26');

Event.remove('某公眾號', fn);

通過這種全局的Event對象,我們可以利用它在兩個模塊間實現(xiàn)通信

并且兩個模塊互不干擾

//偽代碼

module1 = function(){

? ? Event.listen(...);

}

module2 = function(){

? ? Event.trigger(...);

}

完整的觀察者對象

我們上面寫的Event對象還是存在一些問題

只能先“訂閱”再“發(fā)布”

全局對象會產(chǎn)生命名沖突

對于第一點,在我們訂閱了一個公眾號之前,它是同樣會定時推送消息的

我們訂閱后,同樣可以查看歷史推送

所以我們應(yīng)該是實現(xiàn)先發(fā)布再訂閱仍然可以查看歷史消息

對于第二點,如果我們每個人都使用上面我們定義的函數(shù)

所有事件都放在一個list當中

時間長了一定會產(chǎn)生命名沖突

最好的辦法就是為對象增加創(chuàng)建命名空間的功能

現(xiàn)在我們來豐滿我們的事件對象

(這里我就盜用大神寫的代碼了,不同的人不同庫實現(xiàn)的方式都不同)

這個完整版稍微了解一下就好了

var Event = (function(){

? ? var global = this,

? ? ? ? Event,

? ? ? ? _default = 'default';

? ? Event = function(){

? ? ? ? var _listen,

? ? ? ? ? ? _trigger,

? ? ? ? ? ? _remove,

? ? ? ? ? ? _slice = Array.prototype.slice,

? ? ? ? ? ? _shift = Array.prototype.shift,

? ? ? ? ? ? _unshift = Array.prototype.unshift,

? ? ? ? ? ? namespaceCache = {},

? ? ? ? ? ? _create,

? ? ? ? ? ? find,

? ? ? ? ? ? each = function(ary,fn){

? ? ? ? ? ? ? ? var ret;

? ? ? ? ? ? ? ? for(var i = 0, l = ary.length; i < l; i++){

? ? ? ? ? ? ? ? ? ? var n = ary[i];

? ? ? ? ? ? ? ? ? ? ret = fn.call(n,i,n);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? return ret;

? ? ? ? ? ? };

? ? ? ? ? ? _listen = function(key,fn,cache){

? ? ? ? ? ? ? ? if(!cache[key]){

? ? ? ? ? ? ? ? ? ? cache[key] = [];

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? cache[key].push(fn);

? ? ? ? ? ? };

? ? ? ? ? ? _remove = function(key,cache,fn){

? ? ? ? ? ? ? ? if(cache[key]){

? ? ? ? ? ? ? ? ? ? if(fn){

? ? ? ? ? ? ? ? ? ? ? ? for(var i = cache[key].length; i >= 0; i--){

? ? ? ? ? ? ? ? ? ? ? ? ? ? if(cache[key][i] === fn){

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? cache[key].splice(i,1);

? ? ? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? ? ? ? ? cache[key] = [];

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? };

? ? ? ? ? ? _trigger = function(){

? ? ? ? ? ? ? ? var cache = _shift.call(arguments),

? ? ? ? ? ? ? ? ? ? key = _shift.call(arguments),

? ? ? ? ? ? ? ? ? ? args = arguments,

? ? ? ? ? ? ? ? ? ? _self = this,

? ? ? ? ? ? ? ? ? ? ret,

? ? ? ? ? ? ? ? ? ? stack = cache[key];

? ? ? ? ? ? ? ? if(!stack || !stack.length){

? ? ? ? ? ? ? ? ? ? return;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? return each(stack,function(){

? ? ? ? ? ? ? ? ? ? this.apply(_self,args);

? ? ? ? ? ? ? ? });

? ? ? ? ? ? };

? ? ? ? ? ? _create = function(namespace){

? ? ? ? ? ? ? ? var namespace = namespace || _default;

? ? ? ? ? ? ? ? var cache = {},

? ? ? ? ? ? ? ? ? ? offlineStack = [],? //離線事件

? ? ? ? ? ? ? ? ? ? ret = {

? ? ? ? ? ? ? ? ? ? ? ? listen: function(key,fn,last){

? ? ? ? ? ? ? ? ? ? ? ? ? ? _listen(key,fn,cache);

? ? ? ? ? ? ? ? ? ? ? ? ? ? if(offlineStack === null){

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? return;

? ? ? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? ? ? if(last === 'last'){

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? offlineStack.length && offlineStack.pop()();

? ? ? ? ? ? ? ? ? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? each(offlineStack,function(){

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? this();

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? });

? ? ? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? ? ? offlineStack = null;

? ? ? ? ? ? ? ? ? ? ? ? },

? ? ? ? ? ? ? ? ? ? ? ? one: function(key,fn,last){

? ? ? ? ? ? ? ? ? ? ? ? ? ? _remove(key,cache);

? ? ? ? ? ? ? ? ? ? ? ? ? ? this.listen(key,fn,last);

? ? ? ? ? ? ? ? ? ? ? ? },

? ? ? ? ? ? ? ? ? ? ? ? remove: function(key,fn){

? ? ? ? ? ? ? ? ? ? ? ? ? ? _remove(key,cache,fn);

? ? ? ? ? ? ? ? ? ? ? ? },

? ? ? ? ? ? ? ? ? ? ? ? trigger: function(){

? ? ? ? ? ? ? ? ? ? ? ? ? ? var fn,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? args,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? _self = this;

? ? ? ? ? ? ? ? ? ? ? ? ? ? _unshift.call(arguments,cache);

? ? ? ? ? ? ? ? ? ? ? ? ? ? args = arguments;

? ? ? ? ? ? ? ? ? ? ? ? ? ? fn = function(){

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? return _trigger.apply(_self,args);

? ? ? ? ? ? ? ? ? ? ? ? ? ? };

? ? ? ? ? ? ? ? ? ? ? ? ? ? if(offlineStack){

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? return offlineStack.push(fn);

? ? ? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? ? ? return fn();

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? };

? ? ? ? ? ? ? ? ? ? return namespace ?

? ? ? ? ? ? ? ? ? ? ? ? (namespaceCache[namespace] ? namespaceCache[namespace] :

? ? ? ? ? ? ? ? ? ? ? ? ? ? namespaceCache[namespace] = ret)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? : ret;

? ? ? ? ? ? };

? ? ? ? return {

? ? ? ? ? ? create: _create,

? ? ? ? ? ? one: function(key,fn,last){

? ? ? ? ? ? ? ? var event = this.create();

? ? ? ? ? ? ? ? event.one(key,fn,last);

? ? ? ? ? ? },

? ? ? ? ? ? remove: function(key,fn){

? ? ? ? ? ? ? ? var event = this.create();

? ? ? ? ? ? ? ? event.remove(key,fn);

? ? ? ? ? ? },

? ? ? ? ? ? listen: function(key,fn,last){

? ? ? ? ? ? ? ? var event = this.create();

? ? ? ? ? ? ? ? event.listen(key,fn,last);

? ? ? ? ? ? },

? ? ? ? ? ? trigger: function(){

? ? ? ? ? ? ? ? var event = this.create();

? ? ? ? ? ? ? ? event.trigger.apply(this,arguments);

? ? ? ? ? ? }

? ? ? ? };

? ? }();

? ? return Event;

})();

/********* 先發(fā)布后訂閱 *********/

Event.trigger('click',1);

Event.listen('click',function(a){

? ? console.log(a);? //1

});

/********* 使用命名空間 *********/

Event.create('namespace1').listen('click',function(a){

? ? console.log(a);? //1

})

Event.create('namespace1').trigger('click',1);

Event.create('namespace3').listen('click',function(a){

? ? console.log(a);? //2

})

Event.create('namespace3').trigger('click',2);

總結(jié)

觀察者模式有兩個明顯的優(yōu)點

時間上解耦

對象間解耦

它應(yīng)用廣泛,但是也有缺點

創(chuàng)建這個函數(shù)同樣需要內(nèi)存,過度使用會導致難以跟蹤維護

---------------------

作者:_accelerator_

來源:CSDN

原文:https://blog.csdn.net/q1056843325/article/details/53353850

版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 工廠模式類似于現(xiàn)實生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情,實現(xiàn)同樣的效果;這時候需要使用工廠模式。簡單...
    舟漁行舟閱讀 8,140評論 2 17
  • 單例模式 適用場景:可能會在場景中使用到對象,但只有一個實例,加載時并不主動創(chuàng)建,需要時才創(chuàng)建 最常見的單例模式,...
    Obeing閱讀 2,321評論 1 10
  • 找到fullcalendar.js, 找到代碼為 isRTL:false,這句話 輸入以下幾句 monthName...
    迷你小小白閱讀 1,870評論 0 1
  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些閱讀 2,156評論 0 2
  • let Event = (function() { let global = this, Event, _defa...
    夏目祐太閱讀 348評論 0 0

友情鏈接更多精彩內(nèi)容