浅谈node的事件机制


Posted in Javascript onOctober 09, 2017

Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.

在nodejs的官方文档中,明确写出了node的一个特性是event-driven(事件驱动),可见其非常重要。查看源码,我们可知其事件机制为用js写的EventEmitter类,写得非常优雅且应用了发布/订阅模式。

通过实现一个简易的、具有发布/订阅模式的事件机制,以此来理清EventEmitter类的实现思路

Publish/Subscribe(发布/订阅模式)

类比

要讲的是一个模式,模式这个词,听起来就很抽象。我们先举个栗子。假设有一家报纸机构,提供晨报、午报、晚报。如果你想要看某种报纸,你需要向报纸机构订阅,等对应的报纸发布出来后,报纸机构就会通知你来拿报纸。

在这个过程中,报纸机构实现了两个功能,一是接受客户的订阅;二是发布不同类型的报纸。发布报纸的时候,订阅该类型报纸的客户就能接收到通知。

这个报纸机构也就是我们要实现的事件机制。

浅谈node的事件机制

目的

从上面的例子可以看出:1.发布报纸;2.将报纸给到客户;这个连续的过程由于报纸机构的存在,变成了可以先订阅,再发布,等到发布就自动送到客户手中,实现了动作时间上的分离。这也是发布/订阅系统的优势。

实现思路

我们有3种报纸,对应3个事件,每个事件发生时要通知客户。对应的数据格式可以如下:

var Event = {
 morning: event1,
 noon: event2,
 night: event3
}

由于每种报纸都可能有不止一个人订阅,那么格式可优化成这样:

var Event = {
 morning: [e11, e12,...],
 noon: [e21, e22],
 night: event3
}

当用户订阅的时候,我们就将其事件添加对应的数组中;当事件发布的时候,就执行相应事件。说白了就是先存储后使用。

具体代码如下:

1.on表示订阅,将事件添加到对应数组中
2.emit表示发布,将对应数组中的数据取出来执行
3.off表示删除无用的事件

var Event = {
 on: function(key, listener) {
  if (!this.__events) {
   this.__events = {}
  }
  if (!this.__events[key]) {
   this.__events[key] = [];
  } 
  if (_indexOf(this.__events[key], listener) == -1 && typeof listener === 'function') {
   this.__events[key].push(listener)
  }
 },
 emit: function(key) {
  if (!this.__events || !this.__events[key]) return 
  //取得每次订阅不同的参数
  var arg = Array.prototype.slice.call(arguments, 1) || [];

  var listeners = this.__events[key];
  var len = listeners.length;

  for (var i=0; i<len; i++) {
   listeners[i].apply(this, arg)
  }
  return this

 },
 off: function(key, listener) {
  if (!key && !listener) {
   this.__events = {}
  }
  if (key && !listener) {
   delete this.__events[key]
  }
  if (key && listener) {
   var listeners = this.__events[key];
   var index = _indexOf(listeners, listener);
   (index > -1) && listeners.splice(index, 1);
  }
  return this
 }
}

var _indexOf = function(array,key){
 if (array === null) return -1
 var i = 0, length = array.length
 for (; i < length; i++) if (array[i] === key) return i
 return -1
}
//调用
Event.on('console1', function(num) {
 console.log(num); // 1
});

Event.emit('console1', 1)

node的EventEmitter

node的EventEmitter基本逻辑和上面提供的例子基本一样,只是更加复杂些。

1.订阅事件on

function _addListener(target, type, listener, prepend) {
 var m;
 var events;
 var existing;

 if (typeof listener !== 'function')
  throw new TypeError('"listener" argument must be a function');

 events = target._events;
 ...
 if (typeof existing === 'function') {
   // Adding the second element, need to change to array.
   existing = events[type] =
    prepend ? [listener, existing] : [existing, listener];
  } else {
   // If we've already got an array, just append.
   if (prepend) {
    existing.unshift(listener);
   } else {
    existing.push(listener);
   }
  }

 return target;
}

EventEmitter.prototype.addListener = function addListener(type, listener) {
 return _addListener(this, type, listener, false);
};

EventEmitter.prototype.on = EventEmitter.prototype.addListener;

2.发布事件

EventEmitter.prototype.emit = function emit(type) {
 ...
 handler = events[type];
 switch (len) {
  // fast cases
  case 1:
   emitNone(handler, isFn, this);
   break;
  case 2:
   emitOne(handler, isFn, this, arguments[1]);
   break;
  case 3:
   emitTwo(handler, isFn, this, arguments[1], arguments[2]);
   break;
  case 4:
   emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
   break;
  // slower
  default:
   args = new Array(len - 1);
   for (i = 1; i < len; i++)
    args[i - 1] = arguments[i];
   emitMany(handler, isFn, this, args);
 }
}

讲到这里,相信大家已经明白EventEmitter的实现思路。

参考资料

node events.js

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JQuery 学习笔记 选择器之五
Jul 23 Javascript
JQueryEasyUI Layout布局框架的使用
Apr 08 Javascript
JavaScript中的值是按值传递还是按引用传递问题探讨
Jan 30 Javascript
最精简的JavaScript实现鼠标拖动效果的方法
May 11 Javascript
jQuery基础知识点总结(DOM操作)
Jun 01 Javascript
JavaScript基于自定义函数判断变量类型的实现方法
Nov 23 Javascript
js 递归和定时器的实例解析
Feb 03 Javascript
在vue项目中使用element-ui的Upload上传组件的示例
Feb 08 Javascript
讲解vue-router之什么是编程式路由
May 28 Javascript
仿iPhone通讯录制作小程序自定义选择组件的实现
May 23 Javascript
详解vue-video-player使用心得(兼容m3u8)
Aug 23 Javascript
vue props 一次传多个值实例
Jul 22 Javascript
JS实现中文汉字按拼音排序的方法
Oct 09 #Javascript
ES6中的Promise代码详解
Oct 09 #Javascript
js+html5生成自动排列对话框实例
Oct 09 #Javascript
jQuery实现获取table中鼠标click点击位置行号与列号的方法
Oct 09 #jQuery
详解如何让Express支持async/await
Oct 09 #Javascript
jQuery插件artDialog.js使用与关闭方法示例
Oct 09 #jQuery
Node.JS使用Sequelize操作MySQL的示例代码
Oct 09 #Javascript
You might like
2019年中国咖啡业现状与发展趋势
2021/03/04 咖啡文化
一个好用的分页函数
2006/11/16 PHP
php实现的网络相册图片防盗链完美破解方法
2015/07/01 PHP
基于JQuery+PHP编写砸金蛋中奖程序
2015/09/08 PHP
Yii清理缓存的方法
2016/01/06 PHP
php 使用html5实现多文件上传实例
2016/10/24 PHP
php工具型代码之印章抠图
2018/07/18 PHP
PHP创建文件及写入数据(覆盖写入,追加写入)的方法详解
2019/02/15 PHP
innerText和innerHTML 一些问题分析
2009/05/18 Javascript
img标签中onerror用法
2009/08/13 Javascript
获取offsetTop和offsetLeft值的js代码(兼容)
2013/04/16 Javascript
Bootstrap源码解读导航(6)
2016/12/23 Javascript
EasyUI修改DateBox和DateTimeBox的默认日期格式示例
2017/01/18 Javascript
Bootstrap的popover(弹出框)在append后弹不出(失效)
2017/02/27 Javascript
Vue2.0学习之详解Vue 组件及父子组件通信
2017/12/12 Javascript
详解Vue+axios+Node+express实现文件上传(用户头像上传)
2018/08/10 Javascript
在vue中给列表中的奇数行添加class的实现方法
2018/09/05 Javascript
vue中tab选项卡的实现思路
2018/11/25 Javascript
JavaScript常见鼠标事件与用法分析
2019/01/03 Javascript
vue切换菜单取消未完成接口请求的案例
2020/11/13 Javascript
[43:14]Liquid vs Optic 2018国际邀请赛淘汰赛BO3 第二场 8.21
2018/08/22 DOTA
Ubuntu下安装PyV8
2016/03/13 Python
Python地图绘制实操详解
2019/03/04 Python
django2.2 和 PyMySQL版本兼容问题
2020/02/17 Python
Python numpy多维数组实现原理详解
2020/03/10 Python
Python如何省略括号方法详解
2020/03/21 Python
HTML5自定义属性前缀data-及dataset的使用方法(html5 新特性)
2017/08/24 HTML / CSS
iRobot官网:改变生活的家用机器人品牌
2016/09/20 全球购物
新西兰珠宝品牌:Michael Hill
2017/09/16 全球购物
德国团购网站:Groupon德国
2018/03/13 全球购物
对于没有初始化的变量的初始值可以作怎样的假定
2014/10/12 面试题
高校教师岗位职责
2014/03/18 职场文书
《故都的秋》教学反思
2014/04/15 职场文书
售房协议书
2014/08/19 职场文书
详解Python类和对象内容
2021/06/22 Python
Python日志模块logging用法
2022/06/05 Python