首先要感谢eventproxy提供者:JacksonTian(http://weibo.com/shyvo)。node
用node.js去作一些异步的I/O操做时,很容易会写成回调函数深度嵌套,例如:git
var add= function (v1, v2, v3){
console.log(v1+v2+v3+'');
};
var value1,value2,value3
clinet.get("key1", function (err, data) {
// do something
value1 = data
clinet.get("key2", function (
err, data
) {
// do something
value2=data
clinet.get("key3", function (
err, data
) {
//do something
value3 = data
add(value1, value2, value3);
});
});
});
若是咱们使用了eventproxy这个插件,则能够更多关心业务,去掉深度嵌套,而且在一些状况下显著提升效率。
在异步操做返回结果互相不耦合的状况下,使用eventproxy会提升性能。
代码能够改写为:
var EventProxy = require('./eventproxy');
var proxy = new EventProxy();
var add= function (v1, v2, v3){
console.log(v1+v2+v3+'');
};
proxy.assign("v1", "v2", "v3", add);
clinet1.get("key1", function (err, data) {
//do something
proxy.trigger("v1", data);
});
clinet2.get("data", function (err, data) {
//do something
proxy.trigger("v2", data);
});
clinet3.get("l10n", function (err, data) {
//do something
proxy.trigger("v3", data);
});
这样是否是很酷?先执行 proxy.assign("v1", "v2", "v3", render);
来注册整个异步回调结束后的事件,而后再同时对每一个key去作get操做,这些都是异步的哦,因此会比以前按顺序获取key要快一些,固然这个得配合链接池的应用了,这个例子就使用了clinet1-3这3个redis链接。
整个代码风格易于阅读,并且效率会比以前高出一些,那eventproxy是怎么作到的呢?
咱们打开它的源代码来一探究竟把。
assign方法:
//入口方法
EventProxy.prototype.assign = function (eventname1, eventname2, cb) {
//eventname1-n 是事件名, cb是最后的回调函数
var args = [].slice.call(arguments);
//由于arguments只是相似数组的特殊数组,因此须要用slice来将arguments转存到args中
args.push(true);
//上面的转存就是为了用push方法,
这里将true插入args尾部,这
是干什么呢?答案咱们下段揭晓。
_assign.apply(this, args);
//调用_assign方法,运行环境是 EventProxy
return this;
};
_assign函数:
//顺藤摸瓜,咱们来看下_assign函数有什么奥秘
var _assign = function (eventname1, eventname2, cb, once) {
//这里同EventProxy.assign函数同样,只是参数的示例
var proxy = this, length, index = 0, argsLength = arguments.length,
callback, events, isOnce, times = 0, flag = {};
//这里定义一些变量,将函数调用环境this赋值为proxy,也就是EventProxy
if (argsLength < 3) {
return this;
// 若是传入的参数长度错误,则return
}
events = [].slice.apply(arguments, [0, argsLength - 2]);
//将参数eventname1-n转存成events 数组
callback = arguments[argsLength - 2];
//将回调函数(就是例子中的:add函数)存成callback
isOnce = arguments[argsLength - 1];
//答案揭晓拉,以前那个true是用来判断是否一次性的,那什么是一次性呢?咱们继续往下看
if (typeof callback !== "function") {
return this;
}
length = events.length;
//这个length比较重要,是判断须要返回的异步结果总数
var bind = function (key) {
var method = isOnce ? "once" : "bind";
//这里会根据是不是一次性来调用不一样的方法
/*
proxy[method]函数说明:
由于咱们以前传递的是true,因此这里调用 EventProxy.prototype.once 这个方法,将key和回调转存进去
EventProxy.prototype.once 这个方法是干什么的呢?咱们下段分析。
回调函数说明:
因为做者没有对
EventProxy.
_fired这个属性作任何注释,根据这里的代码发现
EventProxy.
_fired属性是存放异步返回结果
存放格式key:value,在本例子中则是{v1:value1, v2:value2, v3: value3},
因此在 proxy.trigger("v1", data); 函数中的第一个参数要和proxy.assign("v1", "v2", "v3", add);的前几个名称相同。
最后flag对象设置key为true。一样做者也没有对flag对象作任何说明,咱们推断出是表示当前这个key已经成功返回
times++表示异步已经返回了多少个结果,注意这里times的做用环境
*/
proxy[method](key,
function (data) {
proxy._fired[key] = proxy._fired[key] || {};
proxy._fired[key].data = data;
if (!flag[key]) {
flag[key] = true;
times++;
}
}
);
};
for (index = 0; index < length; index++) {
bind(events[index]);
//每个eventname1-n都调用一次bind函数,并将其值传进去
}
/*
定义all函数,这个all函数是做为回调函数传入到
EventProxy.prototype.bind中的,咱们先看下这个all函数是干什么的。
*/
var all = function () {
if (times < length) {
return;
//times表明已返回异步结果,length表明共须要返回多少个异步结果,这里若是未所有返回则终止本函数
}
var data = [];
for (index = 0; index < length; index++) {
data.push(proxy._fired[events[index]].data);
//若是异步结果已全返回,按顺序转存EventProxy._fired属性里每一个eventname的值
}
if (isOnce) {
proxy.unbind("all", all);
//又出现了isOnce,若是是true,则接触绑定,unbind函数以后会介绍
}
callback.apply(null, data);
//最后执行回调函数,也就是本例中的add(),将data做为参数传给add
};
proxy.bind("all", all);
//调用EventProxy.prototype.bind方法
};
EventProxy.prototype.once方法:
/*
这里的ev就是eventname的值,本例中的v1,v2
callback就是上段中的bind函数
紧接着调用
EventProxy.prototype.bind方法,将
eventname的值和一个回调传递过去,这个回调先执行一次callback函数
再调用EventProxy.prototype.unbind函数传递
eventname的值和这个
arguments.callee 即这个回调函数自己。
*/
EventProxy.prototype.once = function (ev, callback) {
var self = this;
this.bind(ev,
function () {
callback
.apply(self, arguments);
//这里的绿色部分,表明上段2个绿色
self.unbind(ev, arguments.callee);
}
);
return this;
};
EventProxy.prototype.unbind方法:
/**
原版说明:
* @description Remove one or many callbacks. If `callback` is null, removes all
* callbacks for the event. If `ev` is null, removes all bound callbacks
* for all events.
* @param {string} eventName Event name.
* @param {function} callback Callback.
总结一下,这个方法做用是根据传递的参数不一样来清楚 EventProxy._callback 内的回调函数的
可是使用list[i]=null,这里我的感受不妥,由于将数组值设置为null之后数组的长度是不会改变的,建议改成:
list.splice(i,1);完全删除这个数组元素
*/
EventProxy.prototype.unbind = EventProxy.prototype.removeListener = function(ev, callback) {
var calls;
if (!ev) {
//若是 eventName不存在,则清空EventProxy._callback对象
this._callbacks = {};
} else if (calls = this._callbacks) {
//若是
EventProxy
._callback存在,这个判断感受有点多余,多是为了严谨
if (!callback) {
calls[ev] = [];
//若是不传回调函数,则直接清空EventProxy._callback对象中的eventName属性
} else {
var list = calls[ev];
if (!list) return this;
//这里是容错判断
for (var i = 0, l = list.length; i < l; i++) {
if (callback === list[i]) {
list[i] = null;
//判断传递过来的callback在EventProxy._callback对象的eventName属性中是否存在,若是存在则把改callback清空
break;
}
}
}
}
return this;
};
接下来就是核心方法了,
EventProxy.prototype.bind方法
:
/**
原版说明:
* @description Bind an event, specified by a string name, `ev`, to a `callback` function.
* Passing `"all"` will bind the callback to all events fired.
* @param {string} eventName Event name.
* @param {function} callback Callback.
*/
EventProxy.prototype.bind = EventProxy.prototype.on = EventProxy.prototype.addListener = function(ev, callback) {
this._callbacks = this._callbacks || {};
var list = this._callbacks[ev] || (this._callbacks[ev] = []);
//给list赋值,若是EventProxy._callbacks[ev]不存在,则将EventProxy._callbacks[ev]设置为数组
list.push(callback);
//将回调函数塞入EventProxy._callbacks[ev]
return this;
};
又是改变函数做用域,又是方法互相调来调去好不混乱,无论你有没有晕,反正我是晕了。休息一会,让咱们简单看一下本例中当执行了proxy.assign("v1", "v2", "v3", add);这个方法后,proxy对象改变了那些呢?proxy是EventProxy的一个实例,因此上面代码中出现的EventProxy均可以用proxy来代替了。
首先是proxy._callbacks,他里面会存放为:
{
all:(all函数),
v1:[(上面紫色的函数)],
v2:[(上面紫色的函数)],
v3:[(上面紫色的函数)]
}
午休片刻,我继续上路。
当异步结果回来之后,EventProxy当地又作了什么呢?咱们来看代码:
clinet1.get("key1", function (err, data) {
//do something
proxy.trigger("v1", data);
//异步操做返回之后,调用proxy.trigger传入第一个标识v1和异步结果data;
});
EventProxy.prototype.trigger方法:
/**
原版说明:
* @description Trigger an event, firing all bound callbacks. Callbacks are passed the
* same arguments as `trigger` is, apart from the event name.
* Listening for `"all"` passes the true event name as the first argument.
* @param {string} eventName Event name.
* @param {mix} data Pass in data.
*/
EventProxy.prototype.emit = EventProxy.prototype.fire = EventProxy.prototype.trigger = function(eventName, data, data2) {
var list, calls, ev, callback, args, i, l;
var both = 2;
//好奇怪的2!咱们继续看下去!
if (!(calls = this._callbacks)) return this;
//又见容错代码
while (both--) {
//both是2,这里循环执行2次
ev = both ? eventName : 'all';
//第一次ev是eventname,第二次是all,上行 while (both--) 至关于while (both) 而后执行 both = both -1
if (list = calls[ev]) {
for (i = 0, l = list.length; i < l; i++) {
if (!(callback = list[i])) {
list.splice(i, 1); i--; l--;
//这个也属于容错
} else {
args = both ? Array.prototype.slice.call(arguments, 1) : arguments;
//第一次args是data1-n,第二次是整个参数数组即:eventName,data1-n
callback.apply(this, args);
//第一次依次执行EventProxy._callbacks[eventName]属性数组下的回调,本例只有一个回调执行一次
//第二次依次执行EventProxy._callbacks.all属性数组下的回调,本例只有一个回调执行一次
}
}
}
}
return this;
};
/*
看完上面代码是否是有点懂了
没错就像你所想象的那样,咱们回头看以前绿色的bind函数和all函数
第一次执行while循环将:
一、异步结果保存在proxy._fired中,而后将times+1,标志flag,这个flag可能本意是为了告知用户返回了哪一个异步请求的结果,可是做者并无彻底利用起来。其实能够做为 EventProxy.prototype.trigger的一个返回值给用户。
二、执行unbind方法,将
proxy._callbacks[eventname]下的本回调函数解绑。
第二次执行while循环将:
一、执行上面粗绿色的all函数,根据times和length判断是否已经所有返回。
*/
基本源代码的分析结束了,咱们来总结一下把:
理论上来讲实例化一次 EventProxy 能够屡次享用并不会形成冲突,由于其times和length是放在函数内的局部变量,各个方法之间互不干涉。可是也有局限性,就是eventname标识不能取同名,不然就会冲突了。
EventProxy 的实现很巧妙,虽然代码互相调来调去让人头晕,其实能够稍微改写一下代码,让EventProxy 只有一个入口调用各个功能模块,功能模块之间不要互相调用,充分利用return 返回值,这样代码阅读起来会更加容易。