Arale源码解析(2)——Events

带注释源码

// Regular expression used to split event strings
// 用于分割事件名的正则,识别空格
var eventSplitter = /\s+/


// A module that can be mixed in to *any object* in order to provide it
// with custom events. You may bind with `on` or remove with `off` callback
// functions to an event; `trigger`-ing an event fires all callbacks in
// succession.
//
//     var object = new Events();
//     object.on('expand', function(){ alert('expanded'); });
//     object.trigger('expand');
//
// 介绍使用方法,这个模块可以混入任何对象之中,实现对自定义事件的资瓷~
function Events() {
}


// Bind one or more space separated events, `events`, to a `callback`
// function. Passing `"all"` will bind the callback to all events fired.
// 将空格分割的事件绑定给对象,事件名为all的话,事件回调函数在任何事件被触发时都会调用。
Events.prototype.on = function(events, callback, context) {
  var cache, event, list
  // 回调函数不存在,直接返回
  if (!callback) return this

  // 将对象的`__events`属性缓存,`__events`属性不存在则初始化为空对象
  cache = this.__events || (this.__events = {})
  // 将参数中的事件字符串进行分割,得到事件名数组
  events = events.split(eventSplitter)

  // 循环遍历`events`中的事件
  while (event = events.shift()) {
    // 查询cache中是否缓存了事件,如果有,取得这个事件的回调函数队列的引用,如果没有,初始化为空数组
    list = cache[event] || (cache[event] = [])
    // 将回调和上下文存入回调函数队列
    list.push(callback, context)
  }

  return this
}

// 绑定只执行一次就销毁的事件回调
Events.prototype.once = function(events, callback, context) {
  var that = this
  // 对传入的`callback`进行一次封装,`cb`内调用`off`方法,调用一次就解绑
  var cb = function() {
    that.off(events, cb)
    callback.apply(context || that, arguments)
  }
  // 将封装后的`cb`进行绑定
  return this.on(events, cb, context)
}

// Remove one or many callbacks. If `context` is null, removes all callbacks
// with that function. If `callback` is null, removes all callbacks for the
// event. If `events` is null, removes all bound callbacks for all events.
// 移除一个或多个回调,如果`context`为空,移除所有同名的回调。
// 如果`callback`为空,移除该事件上所有回调。
// 如果`events`为空,移除所有时间上绑定的所有回调函数。
Events.prototype.off = function(events, callback, context) {
  var cache, event, list, i

  // No events, or removing *all* events.
  // 如果没有任何已绑定事件,直接返回
  if (!(cache = this.__events)) return this
  // 如果三个参数都没传,则删除对象上的`__events`属性,并返回对象
  if (!(events || callback || context)) {
    delete this.__events
    return this
  }

  // 对传入的`events`进行分割处理,如果没有传入`events`,取得缓存中的所有事件
  events = events ? events.split(eventSplitter) : keys(cache)

  // Loop through the callback list, splicing where appropriate.
  // 循环遍历events
  while (event = events.shift()) {
    // 保存事件回调队列
    list = cache[event]
    // 如队列为空,跳过
    if (!list) continue

    // 如果`callback`和`context`都没传,则删除该事件队列
    if (!(callback || context)) {
      delete cache[event]
      continue
    }

    // 遍历回调队列,注意每个回调和其调用上下文是间隔排列的,步长为2
    // 和传入的`callback`以及`context`比较,都符合的则将回调和调用上下文从数组中移除
    for (i = list.length - 2; i >= 0; i -= 2) {
      if (!(callback && list[i] !== callback ||
          context && list[i + 1] !== context)) {
        list.splice(i, 2)
      }
    }
  }

  return this
}


// Trigger one or many events, firing all bound callbacks. Callbacks are
// passed the same arguments as `trigger` is, apart from the event name
// (unless you're listening on `"all"`, which will cause your callback to
// receive the true name of the event as the first argument).
Events.prototype.trigger = function(events) {
  var cache, event, all, list, i, len, rest = [], args, returned = true;
  // 如果没有绑定过任何事件,直接返回
  if (!(cache = this.__events)) return this

  // 分割
  events = events.split(eventSplitter)

  // Fill up `rest` with the callback arguments.  Since we're only copying
  // the tail of `arguments`, a loop is much faster than Array#slice.
  // 将除第一个参数`events`外的所有参数保存保存为数组存入`rest`
  for (i = 1, len = arguments.length; i < len; i++) {
    rest[i - 1] = arguments[i]
  }

  // For each event, walk through the list of callbacks twice, first to
  // trigger the event, then to trigger any `"all"` callbacks.
  // 对于每个事件,遍历两次回调队列,第一次是触发那个事件,第二次是触发任何`all`事件的回调
  while (event = events.shift()) {
    // Copy callback lists to prevent modification.
    // 如果缓存中存在all事件,将其回调队列分割存入all
    if (all = cache.all) all = all.slice()
    // 如果缓存中有当前遍历到的事件,将其回调队列分割存入list
    if (list = cache[event]) list = list.slice()

    // Execute event callbacks except one named "all"
    // 当遍历到的事件名不是all时,触发事件的所有回调,以this作为调用上下文
    if (event !== 'all') {
      returned = triggerEvents(list, rest, this) && returned
    }

    // Execute "all" callbacks.
    // 触发对应all事件的所有回调
    returned = triggerEvents(all, [event].concat(rest), this) && returned
  }

  // 返回值
  return returned
}

// trigger == emit
Events.prototype.emit = Events.prototype.trigger


// Helpers
// -------
// 保存对`Object.keys`方法的引用
var keys = Object.keys

// 不存在`Object.keys`方法时就自己实现
if (!keys) {
  // 接受一个对象,返回该对象所有自有属性
  keys = function(o) {
    var result = []

    for (var name in o) {
      if (o.hasOwnProperty(name)) {
        result.push(name)
      }
    }
    return result
  }
}

// Mix `Events` to object instance or Class function.
// 将`Events`混入任何一个Class类的实例
Events.mixTo = function(receiver) {
  // 保存`Events`的原型
  var proto = Events.prototype

  // 判断接收对象类型,是否为构造函数
  if (isFunction(receiver)) {
    // 遍历`Events`原型内方法
    for (var key in proto) {
      // 将自有方法进行复制
      if (proto.hasOwnProperty(key)) {
        receiver.prototype[key] = proto[key]
      }
    }
    // 经过调试这步和上步的作用是一样的,只是调用了ES5的API,不知是否和兼容性有关
    Object.keys(proto).forEach(function(key) {
      receiver.prototype[key] = proto[key]
    })
  }
  else { // 针对接收者不是构造函数而是实例的情况
    // 生成Event类的实例
    var event = new Events
    // 遍历,判断,复制
    for (var key in proto) {
      if (proto.hasOwnProperty(key)) {
        copyProto(key)
      }
    }
  }

  // 复制属性
  function copyProto(key) {
    receiver[key] = function() {
      // 由于receiver已经是一个对象而不是构造函数,所以将所有方法的执行上下文转换为一个Event类的实例
      proto[key].apply(event, Array.prototype.slice.call(arguments))
      return this
    }
  }
}

// Execute callbacks
/**
 * 执行回调的方法
 * @param  {Array} list    回调函数队列
 * @param  {Array} args    参数数组
 * @param  {Object} context 调用上下文
 * @return {Boolean} pass
 */
function triggerEvents(list, args, context) {
  var pass = true

  if (list) {
    var i = 0, l = list.length, a1 = args[0], a2 = args[1], a3 = args[2]
    // call is faster than apply, optimize less than 3 argu
    // http://blog.csdn.net/zhengyinhui100/article/details/7837127
    // 由于`call`方法要比`apply`快,因此针对参数数量少于等于3个的情况进行优化,调用`call`,参数数量大于3个时调用`apply`
    switch (args.length) {
      case 0: for (; i < l; i += 2) {pass = list[i].call(list[i + 1] || context) !== false && pass} break;
      case 1: for (; i < l; i += 2) {pass = list[i].call(list[i + 1] || context, a1) !== false && pass} break;
      case 2: for (; i < l; i += 2) {pass = list[i].call(list[i + 1] || context, a1, a2) !== false && pass} break;
      case 3: for (; i < l; i += 2) {pass = list[i].call(list[i + 1] || context, a1, a2, a3) !== false && pass} break;
      default: for (; i < l; i += 2) {pass = list[i].apply(list[i + 1] || context, args) !== false && pass} break;
    }
  }
  // trigger will return false if one of the callbacks return false
  // 有一个回调函数的返回值为false则pass值为false
  return pass;
}

// 判断是否为Function类型的工具函数
function isFunction(func) {
  return Object.prototype.toString.call(func) === '[object Function]'
}

module.exports = Events

分析

这个Events类的运行方式还是比较简单的,这里就把实现机制归纳一下,这个应该是学习的重点。具体代码层面的实现看源码和注释就行了。

事件的所有相关信息全部保存在对象的__events属性上,该属性值是一个对象,以k-v的形式保存事件名和回调队列的对应关系,结构就像这样:

{
  'click': [callback1, context1, callback2, context2, ...],
  'remove': [callback1, context1, callback2, context2, ...],
  ...
}

一旦触发了某个事件,比如click,那么它对应的回调队列中的所有回调函数就会依次被执行。值得一提的时每个回调函数都有各自的执行上下文对象,这个比较特别,回调和上下文在数组中是间隔排列的,因此触发事件和解除绑定时都会特别处理这种特殊的数据结构。我认为之所以选用数组这种结构主要还是为了保证所有回调的触发顺序可控,如果用对象的话,遍历时的顺序是不一定的。针对这个问题,在玉伯和一位开发者的讨论中也能得到答案。

另外值得一提的是all这个事件,在一个对象上触发任何事件,同时也一定会触发all事件,实现原理很简单,就是在trigger这个方法中,判断一下事件缓存中有没有all这个事件队列,如果有,那么不管触发哪个事件,最后都再触发一下all事件队列即可。

    原文作者:classicemi
    原文地址: https://segmentfault.com/a/1190000000760880
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞