淺談Nodejs觀察者模式
一、前言
Nodejs使用有些日子了,近來再回顧下其API、多使用新特性,以期有更高層次的掌握,本次API的總結(jié)區(qū)別于單純對英文版的漢化,會多做些擴展和自己的理解,希望對大家有所幫助,先從最核心的Events開始
Nodejs的Events實現(xiàn)了一種觀察者模式,其支持了Nodejs的核心機制,且http / fs / mongoose等都繼承了Events,可以添加監(jiān)聽事件。這種設(shè)計模式在客戶端的組件編程思想里經(jīng)常會用到,我們先簡單了解下該模式。
首次接觸 觀察者模式是在Extjs框架的 Ext.util.observable源碼,那時剛接觸js,感覺這種模式很強大,也是我最早接觸到的設(shè)計模式,后來在 underscore.js 源碼里也有看到,且后者實現(xiàn)更簡捷、優(yōu)雅,我編寫組件時也基本是按照這種思想。
觀察者模式就是為某一對象添加一監(jiān)聽事件,如on('show', callback),由該對象在符合條件如show時自行觸發(fā),瀏覽器本身已經(jīng)為dom實現(xiàn)了監(jiān)聽機制。
如我們?yōu)閕nput添加keyup監(jiān)聽,目的是為了輸出其value
$( 'input' ).on( 'keyup', function(){
console.log( this.value );
} );
這樣輸入內(nèi)容時會自行在日志中輸出其value。
但我們自己做一個組件如Dialog,如何監(jiān)聽最常用的show / hide事件呢?
初級的做法是實例化時直接將回調(diào)配置進去,如
var dialog = new Dialog({
content: '這里是彈出框的內(nèi)容',
show: function(){
console.log( '當(dāng)彈框時輸出此段內(nèi)容' );
}
});
這樣也可以用,不過顯然不夠靈活,如何將dialog做的像input那樣可隨時添加事件呢
二、觀察者模式實現(xiàn)
首先實現(xiàn)Events對象,這里提供基礎(chǔ)的監(jiān)聽on和觸發(fā)emit,事件是以json形式壓棧在對象的_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 );
} )
}
}
}
再抽象一個函數(shù)用于為對象復(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 ];
}
}
}
實現(xiàn)一個Dialog,
僅實現(xiàn)創(chuàng)建; method: show / hide; event: show / hide;
看效果時,加上這段樣式
.dialog{
position: fixed;
top: 50%;
left: 50%;
margin: -50px 0 0 -100px;
width: 200px;
height: 120px;
background: #fff;
border: 5px solid #afafaf;
}
實現(xiàn)組件
var Dialog = function( config ){
this.config = config;
this.init( this.config );
};
擴展屬性
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 );
生成實例,并為其添加三個show及hide監(jiān)聽事件
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 );
} );
我們分六次添加了六個不同的show事件和hide事件。
當(dāng)執(zhí)行 dialog.show() 時就會輸出三條對應(yīng)的日志。添加的事件保存在 dialog._events里,如圖

添加的三個show都輸出成功,事件保存在_events屬性里
nodejs Events也是實現(xiàn)了這一過程。
三、結(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: {}, //可以看到實例本身也有_events屬性,添加的監(jiān)聽的事件就保存在這里
_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)聽事件,其它屬性也都顧名思義,就簡單說明下
property
_events: undefined, //以壓棧形式存放on進來的事件
_maxListeners: undefined //設(shè)置最大監(jiān)聽數(shù),超出提warn
----------------------------------------------------------------------------------------------------------------
method
setMaxListeners: [Function: setMaxListeners],
/*設(shè)置私有屬性_maxListeners的值,默認(rèn)Events會在當(dāng)某監(jiān)聽事件多于10個時發(fā)現(xiàn)警告(見上面Events.defaultMaxListeners),以防止內(nèi)存泄露,如
(node) warning: possible EventEmitter memory leak detected. 11 show listeners added. Use emitter.setMaxListeners() to increase limit.
但這只是個友好的提醒,可以通過設(shè)置最大監(jiān)聽數(shù)來規(guī)避這個問題
myEmitter.setMaxListeners( 20 );
*/
emit: [Function: emit],
/*觸發(fā)監(jiān)聽事件
emitter.emit( event, [arg1], [arg2], ... )
如myEmitter.on( 'show', 'prompt content' );
參數(shù)1為事件名,參數(shù)二供on回調(diào)里的參數(shù)
*/
addListener: [Function: addListener],
/*
添加監(jiān)聽事件
emitter.addListener( event, listener );
如 myEmitter.addListener( 'show', function( txt ){ console.log( txt ) } );
參數(shù)一是事件名,參數(shù)二是對應(yīng)的回調(diào),回調(diào)里的參數(shù)就是 emit里的arguments.prototype.slice.call(1);
*/
on: [Function: addListener],
/*
是addListener簡寫
*/
once: [Function: once],
/*
作用同 on,不過emit一次后就失效了
emitter.once( event, listener );
如 myEmitter.once( 'show', function( txt ){ console.log( txt ) } );
當(dāng)myEmitter.emit執(zhí)行第二次時沒有輸出
*/
removeListener: [Function: removeListener],
/*
移除指定事件的指定回調(diào),此時回調(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)聽
myEmitter.removeAllListeners(); //刪除所有監(jiān)聽
*/
listeners: [Function: listeners]
/*
查看指定監(jiān)聽
emitter.listeners( event );
如 myEmitter.listeners( 'show' ); //返回一個數(shù)組
同我們前面使用的 myEmitter._events[ 'show' ]
*/
另外Events類本身提供了一個方法
Events.listenerCount( emitter, event ); 獲取指定實例下指定監(jiān)聽數(shù)
如 Event.listenerCount( myEmitter, 'show' )
-----------------------------------------------------------------------------------------------
還有兩個event
newListener / remoteListener,分別應(yīng)用于為實例添加( on / once )和刪除( removeListener ) 操作。
emitter.on( event, listener );
emitter.on( 'newListener', function( event, listener ){
console.log( emitter.listeners( 'show' ) ); //注意,此時監(jiān)聽還并沒有添加到 emitter.listeners
console.log( arguments );
});
emitter.on( 'removeListener', function(){
console.log( emitter.listeners( 'show' ) );
console.log( arguments );
})
五、應(yīng)用
使用Events,通常就直接實例化即可,如上面API部分所例
不過,如果我們在nodejs端也實現(xiàn)了一個組件,如前面的Dialog,如何讓Dialog也具備Events的功能呢?可以用Extjs實現(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ù),用于實現(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)聽事件show
dialog.on( 'show', function(txt){ console.log( txt )});
//執(zhí)行method show時,就會觸發(fā)其內(nèi)定義的show events,輸出 this is show
dialog.show( 'this is show' );
這樣就為一個組件實現(xiàn)了Events機制,當(dāng)調(diào)用method時,會觸發(fā)event
六、總結(jié)
nodejs提供了很好的監(jiān)聽機制,并且也應(yīng)用在其所有模塊,其支持了nodejs最特色的I/O模式,如我們啟動http服務(wù)時會監(jiān)聽其 connect / close,http.request時會監(jiān)聽 data / end等,了解監(jiān)聽機制對學(xué)習(xí)理解nodejs的基礎(chǔ),也對提升編程思想有益。
- node.js實現(xiàn)的裝飾者模式示例
- 剖析Node.js異步編程中的回調(diào)與代碼設(shè)計模式
- Node.js中使用事件發(fā)射器模式實現(xiàn)事件綁定詳解
- node.js chat程序如何實現(xiàn)Ajax long-polling長鏈接刷新模式
- windows系統(tǒng)下簡單nodejs安裝及環(huán)境配置
- nodejs文件操作模塊FS(File System)常用函數(shù)簡明總結(jié)
- nodejs開發(fā)環(huán)境配置與使用
- NodeJs基本語法和類型
- NodeJS設(shè)計模式總結(jié)【單例模式,適配器模式,裝飾模式,觀察者模式】
相關(guān)文章
手把手教你把nodejs部署到linux上跑出hello world
本篇文章主要介紹了手把手教你把nodejs部署到linux上跑出hello world,非常具有實用價值,需要的朋友可以參考下2017-06-06
基于nodejs+express(4.x+)實現(xiàn)文件上傳功能
通過一段時間的查閱資料發(fā)現(xiàn)實現(xiàn)上傳的方式有:1.express中間件multer模塊2.connect-multiparty模塊(但現(xiàn)在 官方不推薦 )3.使用multiparty模塊實現(xiàn)4.使用formidable插件實現(xiàn),本文給大家介紹nodejs+express(4.x+)實現(xiàn)文件上傳功能,需要的朋友參考下2015-11-11
Node.js中的npm單獨與批量升級依賴包的方式超詳細(xì)講解
npm outdated僅檢查所有已安裝包的依賴關(guān)系,并將當(dāng)前版本遠(yuǎn)程倉庫中的最新版本進行對比,不會升級,這篇文章主要介紹了Node.js中的npm單獨與批量升級依賴包的方式超詳細(xì)講解,需要的朋友可以參考下2024-02-02
Windows部署NVM并下載多版本Node.js的方法(含刪除原有Node的方法)
這篇文章主要介紹了Windows部署NVM并下載多版本Node.js的方法(含刪除原有Node的方法),文中通過圖文結(jié)合的方式講解的非常詳細(xì),對大家了解Node.js有一定的幫助,需要的朋友可以參考下2025-01-01

