對樸大的eventproxy的解讀。
上來就把要用的一些通用方法,抽離出來。
var SLICE = Array.prototype.slice;
var CONCAT = Array.prototype.concat;
var later = (typeof setImmediate !== 'undefined' && setImmediate) ||
(typeof process !== 'undefined' && process.nextTick) || function (fn) {
setTimeout(fn, 0);
};
其中l(wèi)ater是異步的作用,這里是根據(jù)不同的環(huán)境來確定later的,
如果有setImmediate就用setImmediate這個nodejs給我們的異步函數(shù),如果沒有就用process.nextTick這個nodejs的loop函數(shù),如果實在沒有就用timer這個比較消耗資源的setTimeout。
一些常量的定義
var ALL_EVENT = '__all__';
這個ALL_EVENT是個event名稱(代表所有的event都會觸發(fā)的事件名稱)
eventproxy的構(gòu)造函數(shù)
var EventProxy = function () {
// 這個考慮到了如果把這個constuctor當(dāng)做普通的函數(shù)調(diào)用, 那么也返回 new EventProxy
if (!(this instanceof EventProxy)) {
return new EventProxy();
}
// 這個用于記錄event事件和callback關(guān)系的數(shù)據(jù)結(jié)構(gòu)
this._callbacks = {};
// 在all或tail方法中存取相應(yīng)訂閱的data
this._fired = {};
};
這里先提前說一下,其實eventproxy主要就是訂閱/發(fā)布設(shè)計模式的實現(xiàn)。
那么下面的那個方法就是核心的方法之一
訂閱
EventProxy.prototype.addListener = function (ev, callback) {
// 就是往_callbacks上邊根據(jù)事件名稱,綁定方法
this._callbacks[ev] = this._callbacks[ev] || [];
this._callbacks[ev].push(callback);
return this;
};
下面是addListener的一些alias和擴展
/**
* `addListener` alias, `bind`
*/
EventProxy.prototype.bind = EventProxy.prototype.addListener;
/**
* `addListener` alias, `on`
*/
EventProxy.prototype.on = EventProxy.prototype.addListener;
/**
* `addListener` alias, `subscribe`
*/
EventProxy.prototype.subscribe = EventProxy.prototype.addListener;
// 訂閱所有的事件
EventProxy.prototype.bindForAll = function (callback) {
this.bind(ALL_EVENT, callback);
};
和addListener方法相似的另一個方法,和addListener主要區(qū)別這個方法是把訂閱的函數(shù)unshift到相應(yīng)事件array的頭部,而不是push到array的尾部。
下一個方法是removeListener,移除訂閱。
// 用于移除事件的method
EventProxy.prototype.removeListener = function (eventname, callback) {
var calls = this._callbacks;
// 如果沒有事件的名稱,就把所有的事件全部clear掉
if (!eventname) {
this._callbacks = {};
} else {
// 如果沒有指定callback的話,把該事件所有的callback都一起刪掉
if (!callback) {
debug('Remove all listeners of %s', eventname);
calls[eventname] = [];
} else {
// 如果指定了callback,有遞歸一下把該callback原來的位置 null
var list = calls[eventname];
if (list) {
var l = list.length;
for (var i = 0; i < l; i++) {
if (callback === list[i]) {
list[i] = null;
}
}
}
}
}
return this;
};
移除訂閱的一些alias和擴展
EventProxy.prototype.unbind = EventProxy.prototype.removeListener;
EventProxy.prototype.removeAllListeners = function (event) {
return this.unbind(event);
};
EventProxy.prototype.unbindForAll = function (callback) {
this.unbind(ALL_EVENT, callback);
};
發(fā)布
EventProxy.prototype.trigger = function (eventname, data) {
var list, ev, callback, i, l;
var both = 2;
var calls = this._callbacks;
debug('Emit event %s with data %j', eventname, data);
while (both--) {
// 1 為 eventname 0 為 ALL_EVENT
ev = both ? eventname : ALL_EVENT;
list = calls[ev];
if (list) {
for (i = 0, l = list.length; i < l; i++) {
// 如果callback = nil 去掉
// unbind掉的callback
if (!(callback = list[i])) {
list.splice(i, 1);
i--;
l--;
} else {
var args = [];
var start = both ? 1 : 0;
for (var j = start; j < arguments.length; j++) {
args.push(arguments[j]);
}
callback.apply(this, args);
}
}
}
}
return this;
};
這里注意會先觸發(fā)eventName這個事件的所有訂閱函數(shù),然后觸發(fā)ALL_EVENT的所有訂閱函數(shù)。
發(fā)布函數(shù)的alias們
EventProxy.prototype.emit = EventProxy.prototype.trigger;
EventProxy.prototype.fire = EventProxy.prototype.trigger;
其實上面這幾個方法已經(jīng)實現(xiàn)了基礎(chǔ)的訂閱/發(fā)布者模式。
下面是一些擴展,也是解決callback痛點的主要部分。
once訂閱
EventProxy.prototype.once = function (ev, callback) {
var self = this;
// wrapper一下,添加一個unbind
//一個對高階函數(shù)的應(yīng)用
var wrapper = function () {
callback.apply(self, arguments);
self.unbind(ev, wrapper);
};
this.bind(ev, wrapper);
return this;
};
這個其實就是對高階函數(shù)的一個應(yīng)用,wrapper一下callback添加一些邏輯,這里是添加了self.unbind(ev, wrapper)這個邏輯。
emitLater
異步發(fā)布事件,利用了前邊介紹的later方法,代碼如下:
EventProxy.prototype.emitLater = function () {
var self = this;
var args = arguments;
later(function () {
self.trigger.apply(self, args);
});
};
immediate 綁定一個事件,然后立刻觸發(fā)它
EventProxy.prototype.immediate = function (ev, callback, data) {
this.bind(ev, callback);
this.trigger(ev, data);
return this;
};
/**
* `immediate` alias
*/
EventProxy.prototype.asap = EventProxy.prototype.immediate;
all和tail方法
all和tail的helper
// 用于綁定all和tail的helper method
var _assign = function (eventname1, eventname2, cb, once) {
var proxy = this;
// 參數(shù)的個數(shù)
var argsLength = arguments.length;
// 判斷被回調(diào)的次數(shù)
var times = 0;
// 標(biāo)記是否執(zhí)行過
var flag = {};
// 參數(shù)肯定是大于三的
// Check the arguments length.
if (argsLength < 3) {
return this;
}
// 事件 arr
var events = SLICE.call(arguments, 0, -2);
// 回調(diào)函數(shù)
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);
// 用于綁定事件的helper
var bind = function (key) {
var method = isOnce ? "once" : "bind";
proxy[method](key, function (data) {
proxy._fired[key] = proxy._fired[key] || {};
proxy._fired[key].data = data;
if (!flag[key]) {
flag[key] = true;
times++;
}
});
};
// 綁定所有的事件
var length = events.length;
for (var index = 0; index < length; index++) {
bind(events[index]);
}
// 這個是all_event的callback
var _all = function (event) {
// 沒有全部執(zhí)行完(不是所有的事件都執(zhí)行完畢)
if (times < length) {
return;
}
// 如果這個事件沒有執(zhí)行結(jié)束,return
if (!flag[event]) {
return;
}
var data = [];
for (var index = 0; index < length; index++) {
data.push(proxy._fired[events[index]].data);
}
if (isOnce) {
proxy.unbindForAll(_all);
}
debug('Events %j all emited with data %j', events, data);
callback.apply(null, data);
};
proxy.bindForAll(_all);
};
all調(diào)用這個helper用once為true,tail調(diào)用這個helper用once為false。
EventProxy.prototype.all = function (eventname1, eventname2, callback) {
var args = CONCAT.apply([], arguments);
// push true 來確定是once
args.push(true);
_assign.apply(this, args);
return this;
};
/**
* `all` alias
*/
EventProxy.prototype.assign = EventProxy.prototype.all;
EventProxy.prototype.tail = function () {
var args = CONCAT.apply([], arguments);
// 不是一次的
args.push(false);
_assign.apply(this, args);
return this;
};
/**
* `tail` alias, assignAll
*/
EventProxy.prototype.assignAll = EventProxy.prototype.tail;
/**
* `tail` alias, assignAlways
*/
EventProxy.prototype.assignAlways = EventProxy.prototype.tail;
after方法
EventProxy.prototype.after = function (eventname, times, callback) {
if (times === 0) {
callback.call(null, []);
return this;
}
var proxy = this,
// 這個用于存無序的after調(diào)用的結(jié)果
firedData = [];
this._after = this._after || {};
var group = eventname + '_group';
// 這個用于存取有序的group的結(jié)果
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]);
}
}
// 如果是group來emit的,會保持emit的順序,把數(shù)據(jù)放到_after[group].results中
if (name === group) {
times--;
proxy._after[group].results[data.index] = data.result;
if (times < 1) {
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;
};
EventProxy.prototype.group = function (eventname, callback) {
var that = this;
var group = eventname + '_group';
var index = that._after[group].index;
// 這個用于記錄第幾個emit
that._after[group].index++;
return function (err, data) {
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(err, args1, args2, ...)
result: callback ? callback.apply(null, SLICE.call(arguments, 1)) : data
});
};
};
其中g(shù)roup方法是個特殊emit方法,保證了after訂閱函數(shù)得到的數(shù)據(jù)的數(shù)組是有序的。
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]);
}
};
錯誤處理 fail/throw
EventProxy.prototype.fail = function (callback) {
var that = this;
that.once('error', function () {
that.unbind();
// put all arguments to the error handler
// fail(function(err, args1, args2, ...){})
callback.apply(null, arguments);
});
return this;
};
/**
* A shortcut of ep#emit('error', err)
*/
EventProxy.prototype.throw = function () {
var that = this;
that.emit.apply(that, ['error'].concat(SLICE.call(arguments)));
};
done方法
對錯誤處理的一層封裝
EventProxy.prototype.done = function (handler, callback) {
var that = this;
return function (err, data) {
if (err) {
// put all arguments to the error handler
return that.emit.apply(that, ['error'].concat(SLICE.call(arguments)));
}
// callback(err, args1, args2, ...)
var args = SLICE.call(arguments, 1);
if (typeof handler === 'string') {
// getAsync(query, ep.done('query'));
// or
// getAsync(query, ep.done('query', function (data) {
// return data.trim();
// }));
if (callback) {
// only replace the args when it really return a result
return that.emit(handler, callback.apply(null, args));
} else {
// put all arguments to the done handler
//ep.done('some');
//ep.on('some', function(args1, args2, ...){});
return that.emit.apply(that, [handler].concat(args));
}
}
// speed improve for mostly case: `callback(err, data)`
if (arguments.length <= 2) {
return handler(data);
}
// callback(err, args1, args2, ...)
handler.apply(null, args);
};
};
總之,學(xué)習(xí)一下樸大的代碼風(fēng)格,和事件訂閱和發(fā)布的實現(xiàn)。