当前位置:Gxlcms > JavaScript > 浅谈Nodejs观察者模式

浅谈Nodejs观察者模式

时间:2021-07-01 10:21:17 帮助过:12人阅读

一、前言

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

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

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

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

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

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

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

二、观察者模式实现

首先实现Events对象,这里提供基础的监听on和触发emit,事件是以json形式压栈在对象的_events里

  1. var Events = {
  2. on: function( name, callback){
  3. this._events = this._events || {};
  4. this._events[ name ] = this._events[ name ] || [];
  5. this._events[ name ].push( callback );
  6. },
  7. emit: function( name ){
  8. this._events = this._events || {};
  9. var args = Array.prototype.slice.call( arguments, 1 ),
  10. me = this;
  11. if( this._events[ name ] ){
  12. $.each( this._events[ name ], function( k, v ){
  13. v.call( me, args );
  14. } )
  15. }
  16. }
  17. }

再抽象一个函数用于为对象复制属性

  1. function extend( source ){
  2. var args = Array.prototype.slice.call( arguments, 1 );
  3. for( var i = 0, parent; parent = args[i]; i++ ){
  4. for( var prop in parent ){
  5. source[ prop ] = parent[ prop ];
  6. }
  7. }
  8. }

实现一个Dialog,
仅实现创建; method: show / hide; event: show / hide;

看效果时,加上这段样式

  1. .dialog{
  2. position: fixed;
  3. top: 50%;
  4. left: 50%;
  5. margin: -50px 0 0 -100px;
  6. width: 200px;
  7. height: 120px;
  8. background: #fff;
  9. border: 5px solid #afafaf;
  10. }

实现组件

  1. var Dialog = function( config ){
  2. this.config = config;
  3. this.init( this.config );
  4. };

扩展属性

  1. extend( Dialog.prototype, {
  2. init: function( config ){
  3. this.render( config )
  4. },
  5. render: function( config ){
  6. this.el = $( '<div>' ).addClass( 'dialog' );
  7. this.el.html( config.content );
  8. $( 'body' ).append( this.el );
  9. },
  10. show: function( param ){
  11. this.el.fadeIn();
  12. this.emit( 'show', param );
  13. },
  14. hide: function( param ){
  15. this.el.fadeOut();
  16. this.emit( 'hide', param );
  17. }
  18. }, Events );

生成实例,并为其添加三个show及hide监听事件

  1. var dialog = window.dialog = new Dialog({
  2. content: 'dialog one'
  3. });
  4. dialog.on( 'show', function( txt ){
  5. console.log( 'dialog show one ' + txt );
  6. } );
  7. //do something
  8. dialog.on( 'show', function( txt ){
  9. console.log( 'dialog show two ' + txt );
  10. } );
  11. //do something
  12. dialog.on( 'show', function( txt ){
  13. console.log( 'dialog show three ' + txt );
  14. } );
  15. //do something
  16. dialog.on( 'hide', function( txt ){
  17. console.log( 'dialog hide one ' + txt );
  18. } );
  19. //do something
  20. dialog.on( 'hide', function( txt ){
  21. console.log( 'dialog hide two ' + txt );
  22. } );
  23. //do something
  24. dialog.on( 'hide', function( txt ){
  25. console.log( 'dialog hide three ' + txt );
  26. } );

我们分六次添加了六个不同的show事件和hide事件。
当执行 dialog.show() 时就会输出三条对应的日志。添加的事件保存在 dialog._events里,如图

pic

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

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

三、结构

  1. var Events = require( 'events' );
  2. console.log( Events );
  3. /*
输出如下数据,可以看出 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的简写都是为实例添加监听事件,其它属性也都顾名思义,就简单说明下

  1. property
  2. _events: undefined, //以压栈形式存放on进来的事件
  3. _maxListeners: undefined //设置最大监听数,超出提warn
  4. ----------------------------------------------------------------------------------------------------------------
  5. method
  6. setMaxListeners: [Function: setMaxListeners],
  7. /*设置私有属性_maxListeners的值,默认Events会在当某监听事件多于10个时发现警告(见上面Events.defaultMaxListeners),以防止内存泄露,如
  8. (node) warning: possible EventEmitter memory leak detected. 11 show listeners added. Use emitter.setMaxListeners() to increase limit.
  9. 但这只是个友好的提醒,可以通过设置最大监听数来规避这个问题
  10. myEmitter.setMaxListeners( 20 );
  11. */
  12. emit: [Function: emit],
  13. /*触发监听事件
  14. emitter.emit( event, [arg1], [arg2], ... )
  15. 如myEmitter.on( 'show', 'prompt content' );
  16. 参数1为事件名,参数二供on回调里的参数
  17. */
  18. addListener: [Function: addListener],
  19. /*
  20. 添加监听事件
  21. emitter.addListener( event, listener );
  22. 如 myEmitter.addListener( 'show', function( txt ){ console.log( txt ) } );
  23. 参数一是事件名,参数二是对应的回调,回调里的参数就是 emit里的arguments.prototype.slice.call(1);
  24. */
  25. on: [Function: addListener],
  26. /*
  27. 是addListener简写
  28. */
  29. once: [Function: once],
  30. /*
  31. 作用同 on,不过emit一次后就失效了
  32. emitter.once( event, listener );
  33. 如 myEmitter.once( 'show', function( txt ){ console.log( txt ) } );
  34. 当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构建器

  1. var Dialog = function(){
  2. //do something
  3. }
  4. //抽象apply函数,提供属性的深度复制,同上面的extend
  5. function apply( source ){
  6. var args = Array.prototype.slice.call( arguments, 1 );
  7. for( var i = 0, parent; parent = args[i]; i++ ){
  8. for( var prop in parent ){
  9. source[ prop ] = parent[ prop ];
  10. }
  11. }
  12. }
  13. //抽象extend函数,用于实现继承
  14. var extend = function(){
  15. // inline overrides
  16. var io = function(o){
  17. for(var m in o){
  18. this[m] = o[m];
  19. }
  20. };
  21. var oc = Object.prototype.constructor;
  22. return function(sb, sp, overrides){
  23. if(typeof sp == 'object'){
  24. overrides = sp;
  25. sp = sb;
  26. sb = overrides.constructor != oc ? overrides.constructor : function(){sp.apply(this, arguments);};
  27. }
  28. var F = function(){},
  29. sbp,
  30. spp = sp.prototype;
  31. F.prototype = spp;
  32. sbp = sb.prototype = new F();
  33. sbp.constructor=sb;
  34. sb.superclass=spp;
  35. if(spp.constructor == oc){
  36. spp.constructor=sp;
  37. }
  38. sb.override = function(o){
  39. apply(sb, o);
  40. };
  41. sbp.superclass = sbp.supr = (function(){
  42. return spp;
  43. });
  44. sbp.override = io;
  45. apply(sb, overrides);
  46. sb.extend = function(o){return extend(sb, o);};
  47. return sb;
  48. };
  49. }();
  50. //将Events属性继承给Dialog
  51. Dialog = extend( Dialog, Events );
  52. //为Dialog新增 method show,其内触发 event show
  53. Dialog.prototype.show = function( txt ){
  54. this.emit( 'show', txt );
  55. }
  56. var dialog = new Dialog();
  57. //添加监听事件show
  58. dialog.on( 'show', function(txt){ console.log( txt )});
  59. //执行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的基础,也对提升编程思想有益。

人气教程排行