Nodejs API-Events 談觀察者模式

1、前言

Nodejs使用有些日子了,近來再回顧下其API、多使用新特性,以期有更高層次的掌握,本次API的總結區別於單純對英文版的漢化,會多作些擴展和本身的理解,但願對你們有所幫助,先從最核心的Events開始html

Nodejs的Events實現了一種觀察者模式,其支持了Nodejs的核心機制,且http / fs / mongoose等都繼承了Events,能夠添加監聽事件。這種設計模式在客戶端的組件編程思想裏常常會用到,咱們先簡單瞭解下該模式。node

首次接觸 觀察者模式是在Extjs框架的 Ext.util.observable源碼,那時剛接觸js,感受這種模式很強大,也是我最先接觸到的設計模式,後來在 underscore.js 源碼裏也有看到,且後者實現更簡捷、優雅,我編寫組件時也基本是按照這種思想。編程

觀察者模式就是爲某一對象添加一監聽事件,如on('show', callback),由該對象在符合條件如show時自行觸發,瀏覽器自己已經爲dom實現了監聽機制。json

如咱們爲input添加keyup監聽,目的是爲了輸出其value設計模式

$( 'input' ).on( 'keyup', function(){
      console.log( this.value );
} );

這樣輸入內容時會自行在日誌中輸出其value。數組

但咱們本身作一個組件如Dialog,如何監聽最經常使用的show / hide事件呢?瀏覽器

初級的作法是實例化時直接將回調配置進去,如app

var dialog = new Dialog({
    content: '這裏是彈出框的內容',
    show: function(){
        console.log( '當彈框時輸出此段內容' );
    }
});

這樣也能夠用,不過顯然不夠靈活,如何將dialog作的像input那樣可隨時添加事件呢。下面是一個極簡的實現框架

技術新Q羣:435485569,相關文章可參照 [http://www.upopen.cn]dom

2、觀察者模式實現

首先實現Events對象,這裏提供基礎的監聽on和觸發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 );
            } )
        }
    }     
}

再抽象一個函數用於爲對象複製屬性

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 ];
        }
    }
}

實現一個Dialog,
僅實現建立; 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;
}

實現組件

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監聽事件

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事件。
當執行 dialog.show() 時就會輸出三條對應的日誌。添加的事件保存在 dialog._events裏,如圖

pic

添加的三個show都輸出成功,事件保存在_events屬性裏

nodejs Events也是實現了這一過程。

3、結構

var Events = require( 'events' );
console.log( Events );
/*
輸出以下數據,能夠看出 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屬性,添加的監聽的事件就保存在這裏
    _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 }
*/

4、API

其提供的method有on,是addListener的簡寫都是爲實例添加監聽事件,其它屬性也都顧名思義,就簡單說明下

property
_events: undefined,     //以壓棧形式存放on進來的事件
_maxListeners: undefined   //設置最大監聽數,超出提warn

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

method
setMaxListeners: [Function: setMaxListeners],  
/*設置私有屬性_maxListeners的值,默認Events會在當某監聽事件多於10個時發現警告(見上面Events.defaultMaxListeners),以防止內存泄露,如
(node) warning: possible EventEmitter memory leak detected. 11 show listeners added. Use emitter.setMaxListeners() to increase limit.
但這只是個友好的提醒,能夠經過設置最大監聽數來規避這個問題
myEmitter.setMaxListeners( 20 );
*/

emit: [Function: emit],
 /*觸發監聽事件
emitter.emit( event, [arg1], [arg2], ...  )
如myEmitter.on( 'show', 'prompt content'  );
 參數1爲事件名,參數二供on回調裏的參數
 */

addListener: [Function: addListener],
 /*
添加監聽事件
emitter.addListener( event, listener );
如 myEmitter.addListener( 'show', function( txt ){ console.log( txt ) } );
參數一是事件名,參數二是對應的回調,回調裏的參數就是 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 ) } );
當myEmitter.emit執行第二次時沒有輸出
 */

removeListener: [Function: removeListener],
 /*
移除指定事件的指定回調,此時回調不能再用匿名函數。
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],
 /*
 刪除指定事件的全部回調
 emitter.removeAllListeners( [ event ] );
 如 
    myEmitter.removeAllListeners( 'show' );     //刪除全部show監聽
    myEmitter.removeAllListeners();     //刪除全部監聽
 */

listeners: [Function: listeners]
/*
查看指定監聽
emitter.listeners( event );
如 myEmitter.listeners( 'show' ); //返回一個數組
同咱們前面使用的 myEmitter._events[ 'show' ]
*/

另外Events類自己提供了一個方法
Events.listenerCount( emitter, event ); 獲取指定實例下指定監聽數
如 Event.listenerCount( myEmitter, 'show' )

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

還有兩個event
newListener /  remoteListener,分別應用於爲實例添加( on / once )和刪除( removeListener ) 操做。
emitter.on( event, listener );
emitter.on( 'newListener', function( event, listener ){
    console.log( emitter.listeners( 'show' ) );     //注意,此時監聽還並無添加到 emitter.listeners
    console.log( arguments );    
 });

 emitter.on( 'removeListener', function(){
    console.log( emitter.listeners( 'show' ) );
    console.log( arguments );
 })

5、應用

使用Events,一般就直接實例化便可,如上面API部分所例

不過,若是咱們在nodejs端也實現了一個組件,如前面的Dialog,如何讓Dialog也具有Events的功能呢?能夠用Extjs實現的 extend方案

建立Dialog構建器

var Dialog = function(){
    //do something
}

//抽象apply函數,提供屬性的深度複製,同上面的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 能夠用 util.inherits 代替


//抽象extend函數,用於實現繼承
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,其內觸發 event show
Dialog.prototype.show = function( txt ){
    this.emit( 'show', txt );
}

var dialog = new Dialog();

//添加監聽事件show
dialog.on( 'show', function(txt){ console.log( txt )});

//執行method show時,就會觸發其內定義的show events,輸出 this is show
dialog.show( 'this is show' );

這樣就爲一個組件實現了Events機制,當調用method時,會觸發event

6、總結

nodejs提供了很好的監聽機制,而且也應用在其全部模塊,其支持了nodejs最特點的I/O模式,如咱們啓動http服務時會監聽其 connect / close,http.request時會監聽 data / end等,瞭解監聽機制對學習理解nodejs的基礎,也對提高編程思想有益。

相關文章
相關標籤/搜索