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
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞