1.源码缩影

    !(function (name, definition) {
var hasDefine = typeof define === 'function', //检查上下文环境是否为AMD或CMD
hasExports = typeof module !== 'undefined' && module.exports; //检查上下文环境是否为Node if (hasDefine) {
define(definition); //AMD环境或CMD环境
} else if (hasExports) {
module.exports = definition(require('debug')('eventproxy')); //构建为普通Node模块
} else {
this[name] = definition(); //构建在window.name中
}
})('EventProxy', function (debug) {
debug = debug || function () {}; //为debug设置默认值 var EventProxy = function () { //EventProxy构造函数
if (!(this instanceof EventProxy)) { //防止构造函数以call或apply方式被其它对象调用,只能new
return new EventProxy();
}
this._callbacks = {}; //new之后对象属性_callbacks,保存要处理的回调函数
this._fired = {}; //new之后对象属性_fired,保存回调之后的结果
};
EventProxy.prototype... //EventProxy的诸原型函数,所谓原型函数,就是这些函数为new之后对象多共享
EventProxy.create = function () {...}; //EventProxy的属性create,指向普通函数
EventProxy.EventProxy = EventProxy; //EventProxy的属性EventProxy指向自身 return EventProxy; //返回EventProxy构造函数
});

源码解读: 上面大致展示了eventproxy的结构,首先它使用闭包的形式保证了代码的整洁;其次为了适应不同的使用场合,代码中都做了必要的处理,而我们使用最多的无疑是作为nodejs的模块:检查exports,如果存在,则构建为nodejs模块:module.exports = definition(require(‘debug’)(‘eventproxy’));而definition则是function(debug)开始一直到文件结构的函数,其中debug作为调试,暂且不管,剩下的就是EventProxy函构造函数,EventProxy原型函数,EventProxy.create一般函数,最后返回EventProxy函构造函数.于是构建的结果相当于module.exports = EventProxy;所以在其它模块中的require(“eventproxy”)指向的就是EventProxy构造函数.

2.原型函数addListener,别名bind,on,subscribe

    EventProxy.prototype.addListener = function (ev, callback) {
debug('Add listener for %s', ev);
this._callbacks[ev] = this._callbacks[ev] || [];
this._callbacks[ev].push(callback);
return this;
};

源码解读: 该方法接收两个参数:事件名称和回调函数,首先在_callbacks对象上面开辟事件的数组,之后把回调函数放进该数组中.

3.原型函数headbind

    EventProxy.prototype.headbind = function (ev, callback) {
debug('Add listener for %s', ev);
this._callbacks[ev] = this._callbacks[ev] || [];
this._callbacks[ev].unshift(callback);
return this;
};

源码解读: 该方法与addListene方法相同,只不过是把回调函数放在了数组的第一个

4.原型函数removeListener,别名unbind

    EventProxy.prototype.removeListener = function (eventName, callback) {
var calls = this._callbacks;
if (!eventName) {
debug('Remove all listeners');
this._callbacks = {};
} else {
if (!callback) {
debug('Remove all listeners of %s', eventName);
calls[eventName] = [];
} else {
var list = calls[eventName];
if (list) {
var l = list.length;
for (var i = 0; i < l; i++) {
if (callback === list[i]) {
debug('Remove a listener of %s', eventName);
list[i] = null;
}
}
}
}
}
return this;
};

源码解读: 该方法是为移除特定的回调函数:(1)两个参数都不存在,移除所有回调函数(2)eventName存在,callback不存在,移除eventName上的所有函数(3)eventName存在,callback存在,移除eventName上的callback函数.

5.原型函数removeAllListeners

    EventProxy.prototype.removeAllListeners = function (event) {
return this.unbind(event);
};

源码解读: 该方法是为移除event上所有的函数.

6.原型函数bindForAll

    EventProxy.prototype.bindForAll = function (callback) {
this.bind(ALL_EVENT, callback);
};

源码解读: 该方法是为绑定全局回调函数 all为callback

7.原型函数unbindForAll

    EventProxy.prototype.unbindForAll = function (callback) {
this.unbind(ALL_EVENT, callback);
};

源码解读: 该方法是为移除全局回调函数all的callback函数

8.原型函数trigger,别名emit,fire

    EventProxy.prototype.trigger = function (eventName, data) {
var list, ev, callback, args, i, l;
var both = 2;
var calls = this._callbacks;
debug('Emit event %s with data %j', eventName, data);
while (both--) { //执行两次,第一次执行eventName的回调函数,第二次执行全局__all__的回调函数
ev = both ? eventName : ALL_EVENT;
list = calls[ev]; //回调函数列表
if (list) {
for (i = 0, l = list.length; i < l; i++) {
if (!(callback = list[i])) { //如果回调函数不存在,移除
list.splice(i, 1);
i--;
l--;
} else {
args = both ? SLICE.call(arguments, 1) : arguments;
callback.apply(this, args); //非全局回调函数,值传入data,全局传入全部参数
}
}
}
}
return this;
};

源码解读: 该方法是为eventName事件触发函数,或对eventName事件注值函数:(1)事件触发会做两件事情,一是触发该事件的函数数组,把data出入一一执行,二是执行全局回调函数数组,把事件名称和data传进去,它内部会判断是否所有要处理的事件都已处理完毕,处理完毕就执行后面真正的全局回调,如果没有完成则会退出全局回调函数.

9.原型函数once

    EventProxy.prototype.once = function (ev, callback) {
var self = this;
var wrapper = function () {
callback.apply(self, arguments); //回调wrapper时回调源回调函数,再解绑
self.unbind(ev, wrapper);
};
this.bind(ev, wrapper); //绑定的是wrapper,会回调wrapper
return this;
};

源码解读: 该方法是为一次性绑定函数,即回调之后会移除,是最常用的绑定函数,它绑定的不是原有回调函数,而是进行了包装,因为在包装内部要做解绑处理;虽然进行了包装,但是回调的时候传入的参数还是在内部传入远回调函数执行,只是多了一步解绑.

10.临时变量later

    var later = typeof process !== 'undefined' && process.nextTick || function (fn) {
setTimeout(fn, 0);
};

源码解读: 存在process.nextTick,later就等于process.nextTick,没有就用setTimeout.

11.原型函数emitLater

    EventProxy.prototype.emitLater = function () {
var self = this;
var args = arguments;
later(function () {
self.trigger.apply(self, args);
});
};

源码解读: 迟缓触发,参数应为eventName,data.

12.原型函数immediate,别名asap

    EventProxy.prototype.immediate = function (ev, callback, data) {
this.bind(ev, callback);
this.trigger(ev, data);
return this;
};

源码解读: 即时的,绑定事件回调函数,触发

13.临时变量_assign

    var _assign = function (eventname1, eventname2, cb, once) {
var proxy = this;
var argsLength = arguments.length; //参数个数
var times = 0; //已触发注值个数
var flag = {}; //已触发注值标志 // Check the arguments length.
if (argsLength < 3) { //参数小于3,返回
return this;
} var events = SLICE.call(arguments, 0, -2); //取得传入的event数组
var callback = arguments[argsLength - 2]; //取得回调函数
var isOnce = arguments[argsLength - 1]; //取得是否一次调用标志 // Check the callback type.
if (typeof callback !== "function") { //没有回调函数,返回
return this;
}
debug('Assign listener for events %j, once is %s', events, !!isOnce);
var bind = function (key) { //为处理回调函数
var method = isOnce ? "once" : "bind"; //根据标志取得调用绑到的方法
proxy[method](key, function (data) { //绑定事件和回调函数,回调函数为内建,回调的时候会传入data参数
proxy._fired[key] = proxy._fired[key] || {}; //初始化结果保存位置
proxy._fired[key].data = data; //把数据注入_fired中
if (!flag[key]) { //没有回调过,标志置为true,事件回调个数+1
flag[key] = true;
times++;
}
});
}; var length = events.length;
for (var index = 0; index < length; index++) { //为每一个事件绑定回调函数
bind(events[index]);
} var _all = function (event) { //全局回调函数
if (times < length) { //判断是否所有事件都已回调注值
return;
}
if (!flag[event]) { //判断传入的事件回调标志,无必要
return;
}
var data = [];
for (var index = 0; index < length; index++) {
data.push(proxy._fired[events[index]].data); //把所有注入的值放入到data中
}
if (isOnce) { //如果是一次绑定,解绑全局函数
proxy.unbindForAll(_all);
}
debug('Events %j all emited with data %j', events, data);
callback.apply(null, data); //回调用户传入的回调函数,把data出入,完成
};
proxy.bindForAll(_all); //绑定全局回调函数
};

源码解读: 及其重要,真正的assign函数,其会根据用户简单的事件数组,是否一次绑定标志,回调函数,为每一个事件绑定特定的回调函数,当执行这些回调函数的时候,会把触发的值注入,标志此事件回调完成,检查是否都已回调完成,如果是就调用全局回调函数,即把诸多事件注入的值整理出入用户传入的回调函数.

14.原型函数all,别名assign

    EventProxy.prototype.all = function (eventname1, eventname2, callback) {
var args = CONCAT.apply([], arguments);
args.push(true);
_assign.apply(this, args);
return this;
};

源码解读: 一次绑定的_assign函数

15.原型函数fail

    EventProxy.prototype.fail = function (callback) {
var that = this;
that.once('error', function (err) {
that.unbind();
callback.apply(null, arguments);
});
return this;
};

源码解读: 一次绑定error事件,触发的时候会解绑所有回调函数,把错误传入callback执行

16.原型函数tail,别名assignAll,assignAlways

    EventProxy.prototype.tail = function () {
var args = CONCAT.apply([], arguments);
args.push(false);
_assign.apply(this, args);
return this;
};

源码解读: 永久绑定的_assign函数

17.原型函数after

    EventProxy.prototype.after = function (eventName, times, callback) {
if (times === 0) { //容错吧,不太好
callback.call(null, []);
return this;
}
var proxy = this,
firedData = []; //用户自己注值的地方
this._after = this._after || {};
var group = eventName + '_group';
this._after[group] = { //注值的地方
index: 0,
results: []
};
debug('After emit %s times, event %s\'s listenner will execute', times, eventName);
var all = function (name, data) {
if (name === eventName) { //内部未实现,可能是让用户自己实现吧,逻辑简单?
times--;
firedData.push(data);
if (times < 1) {
debug('Event %s was emit %s, and execute the listenner', eventName, times);
proxy.unbindForAll(all);
callback.apply(null, [firedData]);
}
}
if (name === group) { //与原型函数group配合使用
times--; //注值一次,个数-1,感觉放在此处不妥
proxy._after[group].results[data.index] = data.result; //注值
if (times < 1) { //如果times=0,全部注值完成,解绑全局函数,把值传入callback执行
debug('Event %s was emit %s, and execute the listenner', eventName, times);
proxy.unbindForAll(all);
callback.call(null, proxy._after[group].results);
}
}
};
proxy.bindForAll(all); //绑定全局函数
return this;
};

源码解读: 此函数是为相同的事件的处理,好比多个文件的读取,次数就是文件的个数;内部没有为该事件绑定特殊的回调函数,而是永久性的绑定了一个全局函数,所以在每一次该事件的注入,即回调,都是执行的全局函数,二全局函数会执行一次,就注值一次,个数-1,直到个数为0就解绑全局,把所有注入的值出入回调函数执行.

18.原型函数group

    EventProxy.prototype.group = function (eventName, callback) {
var that = this;
var group = eventName + '_group';
var index = that._after[group].index; //取出index,一个变量
that._after[group].index++;
return function (err, data) { //返回的是nodejs的回调函数,只不过帮用户处理了
if (err) {
// put all arguments to the error handler
return that.emit.apply(that, ['error'].concat(SLICE.call(arguments)));
}
that.emit(group, { //注值
index: index, //当前下标
//如果有callback,会返回经过callback处理的data,没有直接返回data
result: callback ? callback.apply(null, SLICE.call(arguments, 1)) : data
});
};
};

源码解读: 此函数为协助after的执行,源码说可以实现事件注入的有序,单我个人认为没有实现,它是类似这样的实现:

var ep = new EventProxy();
ep.after('file', files.length, function (list) {
// Ordered results
});
for (var i = 0; i < files.length; i++) {
fs.readFile(files[i], 'utf-8', ep.group('file'));
}

可是在下面文件注值的循环中也是异步处理的,实际注值的顺序是这些个文件读取完成的顺序,是不确定的;不确定的东西可以说是有序的么,有序是否应该是按照原文件列表的顺序呢,这种方式和name === eventName的处理是没有区别的.

19.原型函数any

    EventProxy.prototype.any = function () {
var proxy = this,
callback = arguments[arguments.length - 1],
events = SLICE.call(arguments, 0, -1),
_eventName = events.join("_"); debug('Add listenner for Any of events %j emit', events);
proxy.once(_eventName, callback); var _bind = function (key) {
proxy.bind(key, function (data) {
debug('One of events %j emited, execute the listenner');
proxy.trigger(_eventName, {"data": data, eventName: key});
});
}; for (var index = 0; index < events.length; index++) {
_bind(events[index]);
}
};

源码解读: 参数如:eventName1,eventName1,callback.它先一次绑定全局函数为callback,也为每一个事件绑定函数,内部会触发全局函数,进而实现任意一个事件触发,就回调.

20.原型函数not

    EventProxy.prototype.not = function (eventName, callback) {
var proxy = this;
debug('Add listenner for not event %s', eventName);
proxy.bindForAll(function (name, data) {
if (name !== eventName) {
debug('listenner execute of event %s emit, but not event %s.', name, eventName);
callback(data);
}
});
};

源码解读: 内部绑定全局函数,如果触发的事件不是eventName,就执行回调.

21.原型函数done

    EventProxy.prototype.done = function (handler, callback) {
var that = this;
return function (err, data) {
if (err) {
return that.emit.apply(that, ['error'].concat(SLICE.call(arguments)));
}
var args = SLICE.call(arguments, 1); if (typeof handler === 'string') {
if (callback) {
return that.emit(handler, callback.apply(null, args));
} else {
return that.emit.apply(that, [handler].concat(args));
}
}
if (arguments.length <= 2) {
return handler(data);
} handler.apply(null, args);
};
};

源码解读: 返回nodejs回调函数,帮用户处理一些错误信息而已:(1)handler是字符串,如果callback存在,则把callback处理的data触发handler事件,如果callback不存在,则直接data触发handler事件;(2)handler是函数,handler(data);(3)handler(arguments)

22.原型函数doneLater

    EventProxy.prototype.doneLater = function (handler, callback) {
var _doneHandler = this.done(handler, callback);
return function (err, data) {
var args = arguments;
later(function () {
_doneHandler.apply(null, args);
});
};
};

源码解读: later + done

23.构造函数属性create

    EventProxy.create = function () {
var ep = new EventProxy();
var args = CONCAT.apply([], arguments);
if (args.length) {
var errorHandler = args[args.length - 1];
var callback = args[args.length - 2];
if (typeof errorHandler === 'function' && typeof callback === 'function') {
args.pop();
ep.fail(errorHandler);
}
ep.assign.apply(ep, args);
}
return ep;
};

源码解读: assign的快捷方式,可以:EventProxy.create(eventName1,eventName2,callback,errorHandler),或者EventProxy.create(eventName1,eventName2,callback).fail(errorHandler)或者把前面的事件放在数组中.

总结:

eventproxy主要是为了解决在nodejs中异步调用深度嵌套的问题,在一定程度上也提高了效率,因为深度嵌套的回调是会产生依赖的,违反了nodejs异步的初衷. eventproxy的设计是基于这样一种场景,比如要在前端显示话题topic的信息,则在后台不但要拿到话题的信息,还要拿到其作者的信息,其所在栏目的信息,其回复的信息等,要拿到所有这些的信息,如果不采用深度嵌套调用的方式,是无法知道什么时候所有的异步都完成了. 基于这样一个场景,作者就想在一个地方存着这些回调的值,好比栏目信息取得了放进去,作者信息取了放进去,可是还是不知道什么时候都取过来了,于是需要检查,取进去一次值就检查一次. eventproxy中_fired就是存值的地方,形如_fired[eventName].data=data;_callbacks是存放回调函数的地方,回调函数是干什么用的,是存值用的.在用户说明了要在一块执行的一组事件后,eventproxy会问每个事件创建回调函数,其接受data参数,形如_callbacks[eventName] = [callback1,callback2];当用户取到某个事件的值时,就触发该事件,传入值,就完成了值的注入,每次注入会检查是否全部注入,全部注入会把全部的值处理为数组放入全局回调函数中回调,全局回调函数即是用户传入的callback.

博客链接地址:http://blog.csdn.net/qq976477610/article/details/24644031

nodeJS之eventproxy源码解读的更多相关文章

  1. Vue 源码解读(1)—— 前言

    当学习成为了习惯,知识也就变成了常识. 感谢各位的 点赞.收藏和评论. 新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog ...

  2. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  3. SDWebImage源码解读 之 NSData+ImageContentType

    第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...

  4. SDWebImage源码解读 之 UIImage+GIF

    第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...

  5. SDWebImage源码解读 之 SDWebImageCompat

    第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...

  6. SDWebImage源码解读_之SDWebImageDecoder

    第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...

  7. SDWebImage源码解读之SDWebImageCache(上)

    第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...

  8. SDWebImage源码解读之SDWebImageCache(下)

    第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...

  9. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

随机推荐

  1. Swift 2.0 字符串学习笔记(建议掌握OC字符串知识的翻阅)

    自己公司开现在使用OC语言在写,但Swift似乎是苹果更推荐使用的开发语言,估计也是未来开发的趋势,自己以前有接触swift,但又由于公司的项目赶,也没有时间去好好地学习这款开发语言.现在年底了,项目 ...

  2. Qt之hello world

    本人使用的是Qt5.7版本的,请读者自主下载安装. 今天首先来进行Qt入门的第一个程序,也是很经典的一个例子.这是在很多的变成语言中都会用到的例子,就是输出helloworld这个信息.Qt中使用的变 ...

  3. 数组&&函数数组

    数组:一次性定义多个同类型的变量,数组在 内存中存储空间必须是连续的(查询比较快)定义数组: int a[]; int[] a;分配空间: a=new int[5]; 自动为数组元素赋以默认值 a[0 ...

  4. ZooKeeper的注意事项

    在ZooKeeper中存储的数据是以字节数组的形式存储的,当用Java程序处理数据时要注意. Ephemeral znodes并不会有child znode 只有parent-znode存在,才能创建 ...

  5. IE浏览器和CSS盒模型

    网页设计中常听的属性名:内容(content).填充(padding).边框(border).边界(margin), CSS盒子模式都具备这些属性. 盒子模型是CSS中一个重要的概念,理解了盒子模型才 ...

  6. 我的Java开发之路

    拉拉溜溜学习了半年了.才发现自己现在才进入面向对象.

  7. shell基础学习系列(一)

    打开文本编辑器,新建一个文件,扩展名为sh(sh代表shell),扩展名并不影响脚本执行. 输入一些代码: #!/bin/bash echo "Hello World !" &qu ...

  8. gridcontrol中LayoutView层叠图片效果

    1.效果图 2.如上图效果,为比较常见的一种需求,一堆物品图片.有时候需要给不同物品标记,图中左上角就是一张标记性图片.在devexpress里面实现起来也比较容易. 3.部分代码: class Pi ...

  9. unity脚本的运行顺序以及单例的实现

    unity引擎把所有脚本先行编译后,在运行的时候一批,一批的函数进行执行. unity脚本自带函数执行顺序如下:将下面脚本挂在任意物体运行即可得到 Awake ->OnEable-> St ...

  10. oracle系列笔记(2)---多表查询

    多表查询     这篇文章主要讲四点: (1)oracle多表查询    (2)SQL99标准的连接查询   (3)子查询     (4)分级查询 oracle多表查询有两种方式,一种是oracle所 ...