视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
关于Nodejs观察者模式的介绍
2020-11-27 19:34:36 责编:小采
文档
 这篇文章主要介绍了浅谈Nodejs观察者模式的相关资料,需要的朋友可以参考下

一、前言

Nodejs使用有些日子了,近来再回顾下其API、多使用新特性,以期有更高层次的掌握,本次API的总结区别于单纯对英文版的汉化,会多做些扩展和自己的理解,希望对大家有所帮助,先从最核心的Events开始

Nodejs的Events实现了一种观察者模式,其支持了Nodejs的核心机制,且http / fs / mongoose等都继承了Events,可以添加监听事件。这种设计模式在客户端的组件编程思想里经常会用到,我们先简单了解下该模式。

首次接触 观察者模式是在Extjs框架的 Ext.util.observable源码,那时刚接触js,感觉这种模式很强大,也是我最早接触到的设计模式,后来在 underscore.js 源码里也有看到,且后者实现更简捷、优雅,我编写组件时也基本是按照这种思想。

观察者模式就是为某一对象添加一监听事件,如on('show', callback),由该对象在符合条件如show时自行触发,浏览器本身已经为dom实现了监听机制。

如我们为input添加keyup监听,目的是为了输出其value

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

这样输入内容时会自行在日志中输出其value。

但我们自己做一个组件如Dialog,如何监听最常用的show / hide事件呢?

初级的做法是实例化时直接将回调配置进去,如

var dialog = new Dialog({
 content: '这里是弹出框的内容',
 show: function(){
 console.log( '当弹框时
输出此段内容' ); } });

这样也可以用,不过显然不够灵活,如何将dialog做的像input那样可随时添加事件呢

二、观察者模式实现

首先实现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 = $( '<p>' ).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里,如图

添加的三个show都输出成功,事件保存在_events属性里

nodejs Events也是实现了这一过程。

三、结构

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 } */

四、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 ); })

五、应用

使用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函数,用于实现继承
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

六、总结

nodejs提供了很好的监听机制,并且也应用在其所有模块,其支持了nodejs最特色的I/O模式,如我们启动http服务时会监听其 connect / close,http.request时会监听 data / end等,了解监听机制对学习理解nodejs的基础,也对提升编程思想有益。

下载本文
显示全文
专题