在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]);

欢迎补充指正!