淺談Nodejs觀察者模式
一、前言
Nodejs使用有些日子了,近來(lái)再回顧下其API、多使用新特性,以期有更高層次的掌握,本次API的總結(jié)區(qū)別于單純對(duì)英文版的漢化,會(huì)多做些擴(kuò)展和自己的理解,希望對(duì)大家有所幫助,先從最核心的Events開(kāi)始
Nodejs的Events實(shí)現(xiàn)了一種觀察者模式,其支持了Nodejs的核心機(jī)制,且http / fs / mongoose等都繼承了Events,可以添加監(jiān)聽(tīng)事件。這種設(shè)計(jì)模式在客戶端的組件編程思想里經(jīng)常會(huì)用到,我們先簡(jiǎn)單了解下該模式。
首次接觸 觀察者模式是在Extjs框架的 Ext.util.observable源碼,那時(shí)剛接觸js,感覺(jué)這種模式很強(qiáng)大,也是我最早接觸到的設(shè)計(jì)模式,后來(lái)在 underscore.js 源碼里也有看到,且后者實(shí)現(xiàn)更簡(jiǎn)捷、優(yōu)雅,我編寫組件時(shí)也基本是按照這種思想。
觀察者模式就是為某一對(duì)象添加一監(jiān)聽(tīng)事件,如on('show', callback),由該對(duì)象在符合條件如show時(shí)自行觸發(fā),瀏覽器本身已經(jīng)為dom實(shí)現(xiàn)了監(jiān)聽(tīng)機(jī)制。
如我們?yōu)閕nput添加keyup監(jiān)聽(tīng),目的是為了輸出其value
$( 'input' ).on( 'keyup', function(){ console.log( this.value ); } );
這樣輸入內(nèi)容時(shí)會(huì)自行在日志中輸出其value。
但我們自己做一個(gè)組件如Dialog,如何監(jiān)聽(tīng)最常用的show / hide事件呢?
初級(jí)的做法是實(shí)例化時(shí)直接將回調(diào)配置進(jìn)去,如
var dialog = new Dialog({ content: '這里是彈出框的內(nèi)容', show: function(){ console.log( '當(dāng)彈框時(shí)輸出此段內(nèi)容' ); } });
這樣也可以用,不過(guò)顯然不夠靈活,如何將dialog做的像input那樣可隨時(shí)添加事件呢
二、觀察者模式實(shí)現(xiàn)
首先實(shí)現(xiàn)Events對(duì)象,這里提供基礎(chǔ)的監(jiān)聽(tīng)on和觸發(fā)emit,事件是以json形式壓棧在對(duì)象的_events里
var Events = { on: function( name, callback){ this._events = this._events || {}; this._events[ name ] = this._events[ name ] || []; this._events[ name ].push( callback ); }, emit: function( name ){ this._events = this._events || {}; var args = Array.prototype.slice.call( arguments, 1 ), me = this; if( this._events[ name ] ){ $.each( this._events[ name ], function( k, v ){ v.call( me, args ); } ) } } }
再抽象一個(gè)函數(shù)用于為對(duì)象復(fù)制屬性
function extend( source ){ var args = Array.prototype.slice.call( arguments, 1 ); for( var i = 0, parent; parent = args[i]; i++ ){ for( var prop in parent ){ source[ prop ] = parent[ prop ]; } } }
實(shí)現(xiàn)一個(gè)Dialog,
僅實(shí)現(xiàn)創(chuàng)建; method: show / hide; event: show / hide;
看效果時(shí),加上這段樣式
.dialog{ position: fixed; top: 50%; left: 50%; margin: -50px 0 0 -100px; width: 200px; height: 120px; background: #fff; border: 5px solid #afafaf; }
實(shí)現(xiàn)組件
var Dialog = function( config ){ this.config = config; this.init( this.config ); };
擴(kuò)展屬性
extend( Dialog.prototype, { init: function( config ){ this.render( config ) }, render: function( config ){ this.el = $( '<div>' ).addClass( 'dialog' ); this.el.html( config.content ); $( 'body' ).append( this.el ); }, show: function( param ){ this.el.fadeIn(); this.emit( 'show', param ); }, hide: function( param ){ this.el.fadeOut(); this.emit( 'hide', param ); } }, Events );
生成實(shí)例,并為其添加三個(gè)show及hide監(jiān)聽(tīng)事件
var dialog = window.dialog = new Dialog({ content: 'dialog one' }); dialog.on( 'show', function( txt ){ console.log( 'dialog show one ' + txt ); } ); //do something dialog.on( 'show', function( txt ){ console.log( 'dialog show two ' + txt ); } ); //do something dialog.on( 'show', function( txt ){ console.log( 'dialog show three ' + txt ); } ); //do something dialog.on( 'hide', function( txt ){ console.log( 'dialog hide one ' + txt ); } ); //do something dialog.on( 'hide', function( txt ){ console.log( 'dialog hide two ' + txt ); } ); //do something dialog.on( 'hide', function( txt ){ console.log( 'dialog hide three ' + txt ); } );
我們分六次添加了六個(gè)不同的show事件和hide事件。
當(dāng)執(zhí)行 dialog.show() 時(shí)就會(huì)輸出三條對(duì)應(yīng)的日志。添加的事件保存在 dialog._events里,如圖
添加的三個(gè)show都輸出成功,事件保存在_events屬性里
nodejs Events也是實(shí)現(xiàn)了這一過(guò)程。
三、結(jié)構(gòu)
var Events = require( 'events' ); console.log( Events ); /* 輸出如下數(shù)據(jù),可以看出 Events指向其EventEmiter { [Function: EventEmitter] EventEmitter: [Circular], usingDomains: [Getter/Setter], defaultMaxListeners: 10, init: [Function], listenerCount: [Function] } */ var myEmitter = new Events(); console.log( myEmitter ); /* { domain: null, _events: {}, //可以看到實(shí)例本身也有_events屬性,添加的監(jiān)聽(tīng)的事件就保存在這里 _maxListeners: undefined} */ console.log( myEmitter.__proto__ ); /* { domain: undefined, _events: undefined, _maxListeners: undefined, setMaxListeners: [Function: setMaxListeners], emit: [Function: emit], addListener: [Function: addListener], on: [Function: addListener], once: [Function: once], removeListener: [Function: removeListener], removeAllListeners: [Function: removeAllListeners], listeners: [Function: listeners] } */ myEmitter.on( 'show', function( txt ){ console.log( 'one ' + txt )}) myEmitter.on( 'show', function( txt ){ console.log( 'tow ' + txt )}) myEmitter.on( 'hide', function( txt ){ console.log( 'one ' + txt )}) myEmitter.emit( 'show', 'show' ); myEmitter.setMaxListeners( 10 ); console.log( myEmitter ); /* { domain: null, _events: { show: [ [Function], [Function] ], hide: [Function] }, //添加后的事情,以json形式存放 _maxListeners: 10 } */
四、API
其提供的method有on,是addListener的簡(jiǎn)寫都是為實(shí)例添加監(jiān)聽(tīng)事件,其它屬性也都顧名思義,就簡(jiǎn)單說(shuō)明下
property _events: undefined, //以壓棧形式存放on進(jìn)來(lái)的事件 _maxListeners: undefined //設(shè)置最大監(jiān)聽(tīng)數(shù),超出提warn ---------------------------------------------------------------------------------------------------------------- method setMaxListeners: [Function: setMaxListeners], /*設(shè)置私有屬性_maxListeners的值,默認(rèn)Events會(huì)在當(dāng)某監(jiān)聽(tīng)事件多于10個(gè)時(shí)發(fā)現(xiàn)警告(見(jiàn)上面Events.defaultMaxListeners),以防止內(nèi)存泄露,如 (node) warning: possible EventEmitter memory leak detected. 11 show listeners added. Use emitter.setMaxListeners() to increase limit. 但這只是個(gè)友好的提醒,可以通過(guò)設(shè)置最大監(jiān)聽(tīng)數(shù)來(lái)規(guī)避這個(gè)問(wèn)題 myEmitter.setMaxListeners( 20 ); */ emit: [Function: emit], /*觸發(fā)監(jiān)聽(tīng)事件 emitter.emit( event, [arg1], [arg2], ... ) 如myEmitter.on( 'show', 'prompt content' ); 參數(shù)1為事件名,參數(shù)二供on回調(diào)里的參數(shù) */ addListener: [Function: addListener], /* 添加監(jiān)聽(tīng)事件 emitter.addListener( event, listener ); 如 myEmitter.addListener( 'show', function( txt ){ console.log( txt ) } ); 參數(shù)一是事件名,參數(shù)二是對(duì)應(yīng)的回調(diào),回調(diào)里的參數(shù)就是 emit里的arguments.prototype.slice.call(1); */ on: [Function: addListener], /* 是addListener簡(jiǎn)寫 */ once: [Function: once], /* 作用同 on,不過(guò)emit一次后就失效了 emitter.once( event, listener ); 如 myEmitter.once( 'show', function( txt ){ console.log( txt ) } ); 當(dāng)myEmitter.emit執(zhí)行第二次時(shí)沒(méi)有輸出 */ removeListener: [Function: removeListener], /* 移除指定事件的指定回調(diào),此時(shí)回調(diào)不能再用匿名函數(shù)。 emitter.removeListener( event, listener ); 如 function show( txt ){ console.log( txt ) }; myEmitter.on( 'show', show ); console.log( myEmitter._events ); // { show: [ Function: show ] } myEmitter.removeListener( 'show', show ); console.log( myEmitter._events ); // {} */ removeAllListeners: [Function: removeAllListeners], /* 刪除指定事件的所有回調(diào) emitter.removeAllListeners( [ event ] ); 如 myEmitter.removeAllListeners( 'show' ); //刪除所有show監(jiān)聽(tīng) myEmitter.removeAllListeners(); //刪除所有監(jiān)聽(tīng) */ listeners: [Function: listeners] /* 查看指定監(jiān)聽(tīng) emitter.listeners( event ); 如 myEmitter.listeners( 'show' ); //返回一個(gè)數(shù)組 同我們前面使用的 myEmitter._events[ 'show' ] */ 另外Events類本身提供了一個(gè)方法 Events.listenerCount( emitter, event ); 獲取指定實(shí)例下指定監(jiān)聽(tīng)數(shù) 如 Event.listenerCount( myEmitter, 'show' ) ----------------------------------------------------------------------------------------------- 還有兩個(gè)event newListener / remoteListener,分別應(yīng)用于為實(shí)例添加( on / once )和刪除( removeListener ) 操作。 emitter.on( event, listener ); emitter.on( 'newListener', function( event, listener ){ console.log( emitter.listeners( 'show' ) ); //注意,此時(shí)監(jiān)聽(tīng)還并沒(méi)有添加到 emitter.listeners console.log( arguments ); }); emitter.on( 'removeListener', function(){ console.log( emitter.listeners( 'show' ) ); console.log( arguments ); })
五、應(yīng)用
使用Events,通常就直接實(shí)例化即可,如上面API部分所例
不過(guò),如果我們?cè)趎odejs端也實(shí)現(xiàn)了一個(gè)組件,如前面的Dialog,如何讓Dialog也具備Events的功能呢?可以用Extjs實(shí)現(xiàn)的 extend方案
創(chuàng)建Dialog構(gòu)建器
var Dialog = function(){ //do something } //抽象apply函數(shù),提供屬性的深度復(fù)制,同上面的extend function apply( source ){ var args = Array.prototype.slice.call( arguments, 1 ); for( var i = 0, parent; parent = args[i]; i++ ){ for( var prop in parent ){ source[ prop ] = parent[ prop ]; } } } //抽象extend函數(shù),用于實(shí)現(xiàn)繼承 var extend = function(){ // inline overrides var io = function(o){ for(var m in o){ this[m] = o[m]; } }; var oc = Object.prototype.constructor; return function(sb, sp, overrides){ if(typeof sp == 'object'){ overrides = sp; sp = sb; sb = overrides.constructor != oc ? overrides.constructor : function(){sp.apply(this, arguments);}; } var F = function(){}, sbp, spp = sp.prototype; F.prototype = spp; sbp = sb.prototype = new F(); sbp.constructor=sb; sb.superclass=spp; if(spp.constructor == oc){ spp.constructor=sp; } sb.override = function(o){ apply(sb, o); }; sbp.superclass = sbp.supr = (function(){ return spp; }); sbp.override = io; apply(sb, overrides); sb.extend = function(o){return extend(sb, o);}; return sb; }; }(); //將Events屬性繼承給Dialog Dialog = extend( Dialog, Events ); //為Dialog新增 method show,其內(nèi)觸發(fā) event show Dialog.prototype.show = function( txt ){ this.emit( 'show', txt ); } var dialog = new Dialog(); //添加監(jiān)聽(tīng)事件show dialog.on( 'show', function(txt){ console.log( txt )}); //執(zhí)行method show時(shí),就會(huì)觸發(fā)其內(nèi)定義的show events,輸出 this is show dialog.show( 'this is show' );
這樣就為一個(gè)組件實(shí)現(xiàn)了Events機(jī)制,當(dāng)調(diào)用method時(shí),會(huì)觸發(fā)event
六、總結(jié)
nodejs提供了很好的監(jiān)聽(tīng)機(jī)制,并且也應(yīng)用在其所有模塊,其支持了nodejs最特色的I/O模式,如我們啟動(dòng)http服務(wù)時(shí)會(huì)監(jiān)聽(tīng)其 connect / close,http.request時(shí)會(huì)監(jiān)聽(tīng) data / end等,了解監(jiān)聽(tīng)機(jī)制對(duì)學(xué)習(xí)理解nodejs的基礎(chǔ),也對(duì)提升編程思想有益。
- node.js實(shí)現(xiàn)的裝飾者模式示例
- 剖析Node.js異步編程中的回調(diào)與代碼設(shè)計(jì)模式
- Node.js中使用事件發(fā)射器模式實(shí)現(xiàn)事件綁定詳解
- node.js chat程序如何實(shí)現(xiàn)Ajax long-polling長(zhǎng)鏈接刷新模式
- windows系統(tǒng)下簡(jiǎn)單nodejs安裝及環(huán)境配置
- nodejs文件操作模塊FS(File System)常用函數(shù)簡(jiǎn)明總結(jié)
- nodejs開(kāi)發(fā)環(huán)境配置與使用
- NodeJs基本語(yǔ)法和類型
- NodeJS設(shè)計(jì)模式總結(jié)【單例模式,適配器模式,裝飾模式,觀察者模式】
相關(guān)文章
node.js中的path.delimiter方法使用說(shuō)明
這篇文章主要介紹了node.js中的path.delimiter方法使用說(shuō)明,本文介紹了path.delimiter的方法說(shuō)明、語(yǔ)法、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下2014-12-12Node.js利用斷言模塊assert進(jìn)行單元測(cè)試的方法
最近在用Node寫一個(gè)實(shí)時(shí)聊天小應(yīng)用,其中就用到了單元測(cè)試,所以死下面這篇文章主要給大家介紹了關(guān)于Node.js利用斷言模塊assert進(jìn)行單元測(cè)試的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-09-09Nodejs做文本數(shù)據(jù)處理實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Nodejs做文本數(shù)據(jù)處理實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Nodejs基于LRU算法實(shí)現(xiàn)的緩存處理操作示例
這篇文章主要介紹了Nodejs基于LRU算法實(shí)現(xiàn)的緩存處理操作,結(jié)合具體實(shí)例形式分析了LRU算法的原理、功能以及nodejs使用LRU算法實(shí)現(xiàn)緩存處理操作的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-03-03nodejs使用Sequelize框架操作數(shù)據(jù)庫(kù)的實(shí)現(xiàn)
這篇文章主要介紹了nodejs使用Sequelize框架操作數(shù)據(jù)庫(kù)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10node.js的exports、module.exports與ES6的export、export default深入詳解
這篇文章主要給大家介紹了關(guān)于node.js中的exports、module.exports與ES6中的export、export default到時(shí)是什么的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10