Vue.js源码分析-- eventsAPI

2016/10/07 Vue

这次我看的是vue源码里的eventsAPI部分,包括$emit/$broadcast/$dispatch等。
eventsAPI源码位置:src/instance/api/events.js

私有函数 modifyListenerCount

var hookRE = /^hook:/
function modifyListenerCount (vm, event, count) {
  var parent = vm.$parent
  // hooks do not get broadcasted so no need
  // to do bookkeeping for them
  if (!parent || !count || hookRE.test(event)) return
  while (parent) {
    parent._eventsCount[event] =
      (parent._eventsCount[event] || 0) + count
    parent = parent.$parent
  }
}

在events.js里边多次调用到该函数,用于向上遍历父组件,更新事件计数器。 <li>组件的_events属性,记录着每个event绑定的回调函数(数组),比如_events[event] = [func1, func2, …]. <li>组件的_eventsCount属性,记录着自己以及子组件对每个event绑定的回调函数的总数目。每当子组件对event事件绑定了n个回调,那父组件(一直向上遍历到根)的_eventsCount[event]会+n。目前发现,_eventsCount在$broadcast会使用到。

Vue.prototype.$on

Vue.prototype.$on = function (event, fn) {
  (this._events[event] || (this._events[event] = []))
    .push(fn)
  modifyListenerCount(this, event, 1)
  return this
}

基础函数,事件监听绑定。组件将回调函数fn保存在_events[event]中,对同一event可以绑定多个回调函数,同时,通过modifyListenerCount更新所有父组件的_eventsCount[event]。

Vue.prototype.$once

Vue.prototype.$once = function (event, fn) {
  var self = this
  function on () {
    self.$off(event, on)
    fn.apply(this, arguments)
  }
  on.fn = fn
  this.$on(event, on)
  return this
}

$once:当event事件发生时,fn只会被调用一次,调用完成后通过$off解除绑定。

Vue.prototype.$off

Vue.prototype.$off = function (event, fn) {
  var cbs
  // all
  if (!arguments.length) {
    if (this.$parent) {
      for (event in this._events) {
        cbs = this._events[event]
        if (cbs) {
          modifyListenerCount(this, event, -cbs.length)
        }
      }
    }
    this._events = {}
    return this
  }
  // specific event
  cbs = this._events[event]
  if (!cbs) {
    return this
  }
  if (arguments.length === 1) {
    modifyListenerCount(this, event, -cbs.length)
    this._events[event] = null
    return this
  }
  // specific handler
  var cb
  var i = cbs.length
  while (i--) {
    cb = cbs[i]
    if (cb === fn || cb.fn === fn) {
      modifyListenerCount(this, event, -1)
      cbs.splice(i, 1)
      break
    }
  }
  return this
}

$off:解除事件绑定,源码可以看出它的三个调用方式:

  • vm.$off() 不带参数:将删除组件所有绑定的事件(this._events = {}),在此之前,会遍历更新父组件的计数器。
  • vm.$off(event) 只带参数event:将删除组件对event绑定的所有事件,同样会遍历更新父组件的计数器。
  • vm.$off(event, fn) 带齐参数event和fn:将删除组件对event事件绑定的fn回调,同样会遍历更新父组件的计数器。 # Vue.prototype.$emit ```javascript Vue.prototype.$emit = function (event) { var isSource = typeof event === 'string' event = isSource ? event : event.name var cbs = this._events[event] var shouldPropagate = isSource || !cbs if (cbs) { cbs = cbs.length > 1 ? toArray(cbs) : cbs // 这里的特殊处理暂且忽略,还得从其他源码推敲 // this is a somewhat hacky solution to the question raised // in #2102: for an inline component listener like <comp @test="doThis">, // the propagation handling is somewhat broken. Therefore we // need to treat these inline callbacks differently. var hasParentCbs = isSource && cbs.some(function (cb) { return cb._fromParent }) if (hasParentCbs) { shouldPropagate = false } var args = toArray(arguments, 1) for (var i = 0, l = cbs.length; i < l; i++) { var cb = cbs[i] var res = cb.apply(this, args) if (res === true && (!hasParentCbs || cb._fromParent)) { shouldPropagate = true } } } return shouldPropagate } ``` ## $emit:用于调用自身对event绑定的回调函数。该函数会被$broadcast和$dispatch调用,所以对参数的event进行了适配。部分变量备注:
  • isSource:是否是源组件发出的$emit事件。也就是说,只有直接调用vm.$emit事件或者$dispatch率先触发自己绑定的回调($dispatch源码第一行)的时候,参数是event字符串,此时isScource才为true。其他情况,如$broadcast内部调用$emit,其参数会是一个非字符串,在下面的$broadcast和$dispatch可以看到,此时的参数会是{ name: event, source: this }。
  • event:由isSource可以得到:event即事件(字符串)。
  • shouldPropagate:是否需要继续传播事件触发。源码中,遍历了event绑定的事件,除开(!hasParentCbs || cb._fromParent)这个不说,只要执行的绑定事件明确return true,shouldPropagate才会置为true。对于$progress,如果shouldPropagate为true,会触发继续向下传播事件。
  • Search

      Table of Contents