一、前言

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 = $( '<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也是实现了这一过程。

三、结构

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的基础,也对提升编程思想有益。

浅谈Nodejs观察者模式的更多相关文章

  1. ios – Objective-C管理观察者的设计模式

    有一种方法来检测UI控制器是否由父控制器发布,而不使用viewWilldisappear方法?有最好的做法来解决这种情况吗?

  2. Swift设计模式之观察者模式

    转自Swift设计模式原文Design-Patterns-In-Swift

  3. 观察者模式 swift

    在MVC里,观察者模式意味着需要允许Model对象和View对象进行交流,而不能有直接的关联。Cocoa使用两种方式实现了观察者模式:Notification和Key-ValueObserving。Apple对于通知的使用很频繁,比如当键盘弹出或者收起的时候,系统会分别发送UIKeyboardWillShowNotification/UIKeyboardWillHideNotification的通知。在LibaratyAPI.swift里加上取消订阅的代码:deinit{NSNotificationCen

  4. KVO原理分析及使用进阶

    本篇文章对KVO的实现原理进行了详细的分析,并且简单的实现了一个KVO,来当做技术交流。概述KVO全称keyvalueObserving,是苹果提供的一套事件通知机制。由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。实际应用KVO主要用来做键值观察操作,想要一个值发生改变后通知另一个对象,则用KVO实现最为合适。下面是KVO前后打印的关键信息,我们在下面做详细分析。

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 浅析Nodejs npm常用命令

    这篇文章主要介绍了浅析Nodejs npm常用命令的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下

  7. nodejs 使用nodejs-websocket模块实现点对点实时通讯

    这篇文章主要介绍了nodejs 使用nodejs-websocket模块实现点对点实时通讯的实例代码,代码简单易懂,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下

  8. nodeJs链接Mysql做增删改查的简单操作

    本篇文章主要介绍了nodeJs链接Mysql做增删改查的简单操作,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  9. Nodejs中使用captchapng模块生成图片验证码

    本篇文章主要介绍了Nodejs中使用captchapng模块实现图片验证码,非常具有实用价值,需要的朋友可以参考下

  10. nodejs 图片预览和上传的示例代码

    本篇文章主要介绍了nodejs 图片预览和上传的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

随机推荐

  1. Error: Cannot find module ‘node:util‘问题解决

    控制台 安装 Vue-Cli 最后一步出现 Error: Cannot find module 'node:util' 问题解决方案1.问题C:\Windows\System32>cnpm install -g @vue/cli@4.0.3internal/modules/cjs/loader.js:638 throw err; &nbs

  2. yarn的安装和使用(全网最详细)

    一、yarn的简介:Yarn是facebook发布的一款取代npm的包管理工具。二、yarn的特点:速度超快。Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。超级安全。在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。超级可靠。使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。三、y

  3. 前端环境 本机可切换node多版本 问题源头是node使用的高版本

    前言投降投降 重头再来 重装环境 也就分分钟的事 偏要折腾 这下好了1天了 还没折腾出来问题的源头是node 使用的高版本 方案那就用 本机可切换多版本最终问题是因为nodejs的版本太高,导致的node-sass不兼容问题,我的node是v16.14.0的版本,项目中用了"node-sass": "^4.7.2"版本,无法匹配当前的node版本根据文章的提

  4. nodejs模块学习之connect解析

    这篇文章主要介绍了nodejs模块学习之connect解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. Node.js编写爬虫的基本思路及抓取百度图片的实例分享

    这篇文章主要介绍了Node.js编写爬虫的基本思路及抓取百度图片的实例分享,其中作者提到了需要特别注意GBK转码的转码问题,需要的朋友可以参考下

  8. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  10. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

返回
顶部