在imagesloaded源码分析部分, 将EvEmitter单独分析(版本^1.0.0
).
首先看下基本的使用情况(为了方便, 直接在node环境演示):
var EventEmitter = require('./ev-emitter.js');
// create event emitter
var emitter = new EventEmitter();
// listeners
function hey(a, b, c) {
console.log('Hey', a, b, c);
}
function ho(a, b, c) {
console.log('Ho', a, b, c);
}
function letsGo(a, b, c) {
console.log('Lets go', a, b, c);
}
// bind listeners
emitter.on('rock', hey);
// 这里作者用的once, 本意应该是想给下面一句用once
emitter.on('rock', ho);
// trigger letsGo once
//! 注意, 官网这里用的on, 本意应该是用once
emitter.once('rock', letsGo);
// emit event
emitter.emitEvent('rock', [1, 2, 3]);
// => 'Hey', 1, 2, 3
// => 'Ho', 1, 2, 3
// => 'Lets go', 1, 2, 3
// unbind
emitter.off('rock', ho);
// => 'Hey' 4, 5, 6
emitter.emitEvent('rock', [4, 5, 6]);
开始分析:
源码结构
- 注释
- UMD写法(可以参看我的另一篇文章)
- 构造函数声明
- 缓存原型
- on 原型方法
- once 原型方法
- off 原型方法
- emitEvent 原型方法
- allOff 原型方法
注释包括两部分:
第一部分包括: 版本, 描述(Lil’是little的缩写, 遵守的许可证明(MIT)
第二部分包括: jshint的选项说明
整体结构如下
/**
* EvEmitter v1.1.0
* Lil' event emitter
* MIT License
*/
/* jshint unused: true, undef: true, strict: true */
// 首先是UMD写法, 兼容AMD, CommonJS及浏览环境,
// 这也是多数库的开头, 无须多解释:
( function( global, factory ) {
...
}( typeof window != 'undefined' ? window : this, function() {
// 使用严格模式
"use strict";
// 构造函数声明
function EvEmitter() {}
// 缓存原型
var proto = EvEmitter.prototype;
// 在原型上添加on方法
proto.on = function(eventName, listener) { ... }
// 在原型上添加once方法
proto.once = function(eventName, listener) { ... }
// 在原型上添加off方法
proto.off = function(eventName, listener) { ... }
// 在原型上添加emitEvent方法
proto.emitEvent = function(eventName, args) { ... }
// 在原型上添加allOff方法
proto.allOff =
proto.removeAllListeners = function() { ... }
// 导出
return EvEmitter;
}));
可以看到, 这个小型的库结构非常清晰, 功能也很明确, 观察者模式清晰的实现.
构造函数及缓存原型(为了执行效率)没啥说的, 跳过.
下面的源代码中, 作者的注释我没有删除, 方便对比
on方法(注册事件)
proto.on = function(eventName, listener) {
// 参数检查
if (!eventName || !listener) {
return;
}
/*
事件集合, 结构如下:
this._events = {
event1: [listener1, listener2...],
event2: [listener1, listener2...]
};
*/
// set events hash
var events = this._events = this._events || {};
// set listeners array
var listeners = events[eventName] = events[eventName] || [];
// 相同的监听器只允许添加一次
// 注意这个对匿名函数无效
// only add once
if (listeners.indexOf(listener) == -1) {
listeners.push(listener);
}
// 方便链式调用
return this;
};
once方法(添加只执行一次的监听器)
proto.once = function(eventName, listener) {
if (!eventName || !listener) {
return;
}
// 正常注册事件
// add event
this.on(eventName, listener);
/*
一次性事件集合, 结构如下:
由于只能执行一次, 所以listener部分不是数组
注意[listener.toString()]表示以listener这个函数的字符串形式作为key
this._onceEvents = {
event: {
[listener.toString()]: true
}
};
*/
// set once flag
// set onceEvents hash
var onceEvents = this._onceEvents = this._onceEvents || {};
// set onceListeners object
var onceListeners = onceEvents[eventName] = onceEvents[eventName] || {};
// 设置标志, 说明只执行一次, 在emitEvent中会用到
// set flag
onceListeners[listener] = true;
return this;
};
off方法(取消事件)
proto.off = function(eventName, listener) {
// 取得事件eventName的监听器队列
var listeners = this._events && this._events[eventName];
if (!listeners || !listeners.length) {
return;
}
// 找到要删除的事件, 并删除
var index = listeners.indexOf(listener);
if (index != -1) {
listeners.splice(index, 1);
}
return this;
};
emitEvent方法(发布/触发事件)
proto.emitEvent = function(eventName, args) {
// 取得事件eventName的监听器队列
var listeners = this._events && this._events[eventName];
if (!listeners || !listeners.length) {
return;
}
// 对监听器队列拷贝执行, 避免干扰, 不懂
// copy over to avoid interference if .off() in listener
listeners = listeners.slice(0);
// 触发事件时可以传参数
args = args || [];
// 当前事件eventName上的一次性监听器
// once stuff
var onceListeners = this._onceEvents && this._onceEvents[eventName];
// 遍历监听器队列
// 这里的listeners.length不应缓存
// 因为对于一次性监听器会被off掉, 导致listeners.length变化
for (var i = 0; i < listeners.length; i++) {
// 当前要执行的监听器
var listener = listeners[i]
// 是否是一次性的
var isOnce = onceListeners && onceListeners[listener];
if (isOnce) {
// 删除一次性监听器, 保证下次emitEvent时不会调用
// remove listener
// remove before trigger to prevent recursion
this.off(eventName, listener);
// 重置flag, 或者用onceListeners[listener]也行
// unset once flag
delete onceListeners[listener];
}
// 执行listener, 并传入参数
// 所以上述的emitter.emitEvent('rock', [1, 2, 3]);
// 在监听器(如hey)中也就是, a = 1, b = 2, c = 3
// trigger listener
listener.apply(this, args);
}
return this;
};
allOff方法(清空事件集合)
removeAllListeners这个方法我看是后面版本加的,
估计是为了更好的语义吧
proto.allOff =
proto.removeAllListeners = function() {
delete this._events;
delete this._onceEvents;
};
看完源码, 我们发现, 开始的例子其实可以写得更简洁些:
var EventEmitter = require('./ev-emitter.js');
// create event emitter
var emitter = new EventEmitter();
// listeners
function hey(a, b, c) {
console.log('Hey', a, b, c);
}
function ho(a, b, c) {
console.log('Ho', a, b, c);
}
function letsGo(a, b, c) {
console.log('Lets go', a, b, c);
}
// bind listeners
emitter
.on('rock', hey)
.on('rock', ho)
.once('rock', letsGo)
.emitEvent('rock', [1, 2, 3])
.off('rock', ho)
.emitEvent('rock', [4, 5, 6]);
欢迎补充指正!