All files / libs/lang Events.js

100% Statements 55/55
100% Branches 26/26
100% Functions 14/14
100% Lines 53/53
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141                                                            10x                 1x 13x 13x 12x 12x 12x   13x 13x 13x 13x 13x 13x               1x 1x                 1x 8x 8x 1x 1x   7x 7x 11x 11x 11x                 1x 11x 11x   11x 11x 11x 10x 7x 7x   3x                     1x 3x 3x 3x 3x   3x 3x 12x 12x 12x           31x 31x 31x 31x 31x   31x       3x     1x  
/**
 *
 * @module      libs/lang/Events
 * @createdAt   2017-03-01
 *
 * @copyright   Copyright (c) 2017 Zhonglei Qiu
 * @license     Licensed under the MIT license.
 */
 
/**
 * @class
 * @example 直接使用
 * var e = new Events()
 * e.on('click', function () { ... })
 * e.emit('click', 1, 2, 3)
 *
 * @example 嫁接到函数或类上
 * function Foo() { ... }
 * Events.mixin(Foo)
 * var f = new Foo()
 * f.on('click', ...)
 * f.emit('click', ...)
 *
 * @example 嫁接到一个对象上
 * var obj = {a: 'aa'}
 * Events.mixin(obj)
 * obj.on('click', ...)
 * obj.emit('click', ...)
 */
function Events() {
  this._events = {}
}
 
/**
 * @param {string} events 事件名称,类似于 jQuery,如果有多个要用空格隔开,也可以添加 namespace
 * @param {*} [data]
 * @param {function} handler
 * @param {boolean} [once]
 */
Events.prototype.on = function(events, data, handler, once) {
  var store = this._events
  if (typeof data === 'function') {
    once = handler
    handler = data
    data = void 0
  }
  parseEventsString(events).forEach(function(event) {
    store[event.type] = store[event.type] || []
    event.handler = handler
    event.data = data
    event.once = !!once
    store[event.type].push(event)
  })
}
 
/**
 * 和 Events.on 类似,只不过它执行完马上就会被销毁
 * @borrows Events.on
 */
Events.prototype.once = function(events, data, handler) {
  this.on.apply(this, Array.prototype.slice.call(arguments).concat(true))
}
 
/**
 * @param {string} [events] 事件名称,类似于 jQuery,如果有多个要用空格隔开,也可以添加 namespace;
 *                          如果不指定 namespace,则会删除所有 namespace 下的相关的 handler;
 *                          如果不指定 events,则会删除所有事件
 * @param {function} [handler] 如果指定了,则删除对应的事件,否则删除所有匹配 events 的事件
 */
Events.prototype.off = function(events, handler) {
  var store = this._events
  if (events == null) {
    this._events = {}
    return
  }
  parseEventsString(events).forEach(function(event) {
    store[event.type] = (store[event.type] || []).filter(function(storeEvent) {
      var isHandlerMatch = !handler || handler === storeEvent.handler
      var isNamespaceMatch = !event.namespace || event.namespace === storeEvent.namespace
      return !isHandlerMatch || !isNamespaceMatch
    })
  })
}
 
/**
 * @param {string} events 事件名称,类似于 jQuery,如果有多个要用空格隔开,也可以添加 namespace
 * @param {...*} args 函数列表,将会传给 handler
 */
Events.prototype.emit = function(events, args) {
  var store = this._events
  args = Array.prototype.slice.call(arguments, 1)
 
  setTimeout(function() {
    parseEventsString(events).forEach(function(event) {
      store[event.type] = (store[event.type] || []).filter(function(storeEvent) {
        if (!event.namespace || event.namespace === storeEvent.namespace) {
          storeEvent.handler.apply(storeEvent, args)
          return !storeEvent.once
        }
        return true
      })
    })
  }, 0)
}
 
/**
 * 使 target 拥有事件相关功能
 *
 * @param {object} target 要将事件相关的函数放置到的对象
 */
Events.mixin = function(target) {
  var props = ['on', 'once', 'off', 'emit']
  var ref = typeof target === 'function' ? target.prototype : target
  var prop = '_events'
  if (prop in ref) warnPropExists(target, prop) // 避免 _events 名称被占用了
 
  Events.call(ref)
  for (var i = 0; i < props.length; i++) {
    prop = props[i]
    if (prop in ref) warnPropExists(target, prop)
    ref[prop] = Events.prototype[prop]
  }
}
 
function parseEventsString(events) {
  var i, parts
  var result = []
  events = events.split(/\s+/)
  for (i = 0; i < events.length; i++) {
    parts = events[i].split('.')
    result[i] = {type: parts[0], namespace: parts.slice(1).join('.')}
  }
  return result
}
 
function warnPropExists(target, prop) {
  console.warn('Target %o has already contains property %o, it will be overwrited!', target, prop)
}
 
module.exports = Events