对朴大的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);
};
其中later是异步的作用,这里是根据不同的环境来确定later的,
如果有setImmediate就用setImmediate这个nodejs给我们的异步函数,如果没有就用process.nextTick这个nodejs的loop函数,如果实在没有就用timer这个比较消耗资源的setTimeout。
一些常量的定义
var ALL_EVENT = '__all__';
这个ALL_EVENT是个event名称(代表所有的event都会触发的事件名称)
eventproxy的构造函数
var EventProxy = function () {
// 这个考虑到了如果把这个constuctor当做普通的函数调用, 那么也返回 new EventProxy
if (!(this instanceof EventProxy)) {
return new EventProxy();
}
// 这个用于记录event事件和callback关系的数据结构
this._callbacks = {};
// 在all或tail方法中存取相应订阅的data
this._fired = {};
};
这里先提前说一下,其实eventproxy主要就是订阅/发布设计模式的实现。
那么下面的那个方法就是核心的方法之一
订阅
EventProxy.prototype.addListener = function (ev, callback) {
// 就是往_callbacks上边根据事件名称,绑定方法
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主要区别这个方法是把订阅的函数unshift到相应事件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);
};
发布
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;
};
这里注意会先触发eventName这个事件的所有订阅函数,然后触发ALL_EVENT的所有订阅函数。
发布函数的alias们
EventProxy.prototype.emit = EventProxy.prototype.trigger;
EventProxy.prototype.fire = EventProxy.prototype.trigger;
其实上面这几个方法已经实现了基础的订阅/发布者模式。
下面是一些扩展,也是解决callback痛点的主要部分。
once订阅
EventProxy.prototype.once = function (ev, callback) {
var self = this;
// wrapper一下,添加一个unbind
//一个对高阶函数的应用
var wrapper = function () {
callback.apply(self, arguments);
self.unbind(ev, wrapper);
};
this.bind(ev, wrapper);
return this;
};
这个其实就是对高阶函数的一个应用,wrapper一下callback添加一些逻辑,这里是添加了self.unbind(ev, wrapper)这个逻辑。
emitLater
异步发布事件,利用了前边介绍的later方法,代码如下:
EventProxy.prototype.emitLater = function () {
var self = this;
var args = arguments;
later(function () {
self.trigger.apply(self, args);
});
};
immediate 绑定一个事件,然后立刻触发它
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;
// 参数的个数
var argsLength = arguments.length;
// 判断被回调的次数
var times = 0;
// 标记是否执行过
var flag = {};
// 参数肯定是大于三的
// Check the arguments length.
if (argsLength < 3) {
return this;
}
// 事件 arr
var events = SLICE.call(arguments, 0, -2);
// 回调函数
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) {
// 没有全部执行完(不是所有的事件都执行完毕)
if (times < length) {
return;
}
// 如果这个事件没有执行结束,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调用这个helper用once为true,tail调用这个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调用的结果
firedData = [];
this._after = this._after || {};
var group = eventname + '_group';
// 这个用于存取有序的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]);
}
}
// 如果是group来emit的,会保持emit的顺序,把数据放到_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
});
};
};
其中group方法是个特殊emit方法,保证了after订阅函数得到的数据的数组是有序的。
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);
};
};
总之,学习一下朴大的代码风格,和事件订阅和发布的实现。