首先要感謝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 返回值,這樣代碼閱讀起來會更加容易。