《一天读一个Vue源文件》--events.js

前情回顾

上篇文章大致分析了Vue 的init方法,大致的流程是,构造函数接收options,然后构造函数中调用this._init(options),之后通过initMixin(Vue)方法,在构造函数的原型上添加_init(options)方法。_init方法的主要功能是合并options,同时设置proxy。代码框架如下:

 
// index.js
function Vue(options){

  this._init(options)
  // init相关
  initMixin(vue)
  // 事件相关
  eventsMixin(Vue)
  
}
// init.js
export function initMixin(Vue){
  Vue.prototype._init = function(options){
    const vm = this;
    // 合并options
    if(options){
      mergeOptions()
    }
    // 设置代理
    initProxy(vm)
    // 生命周期
    initLifecycle(vm)
    
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    ...
    
    // 最后调用$mount()
    if(vm.$options.el){
      vm.$mount(vm.$options.el)
    }
  }
}
 

initLifecycle(vm)

initLifecycle(vm)定义在src/core/instance/lifecycle.js中。该方法似乎什么也没做,只是在Vue的私有属性上添加了默认值。该方法代码如下:

 
 export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}
可以看出,该方法只是设置了vm$parent,$root,$children,$refs,_watcher,__isMounted,_isDestroyed,_isbeingDestroyed的默认值。

重点内容

执行完initLifecycle(vm)添加了属性的默认值之后,接着执行了initEvent(vm)方法,然而我们先不考虑initEvent(vm),重点关注一下,文章开头的eventsMixin(Vue)

之前也知道vue的事件系统是基于发布订阅模式,也知道发布订阅模式的简单实现如下:

  
let event = {
    listener:[],
    add:(name,fn)=>{
      if( !this.listener[ name ] ){ 
        this.listener[name] = [] 
    }
      this.listener[name].push(fn)
    },
    trigger:function (key)=>{
      let fns = this.listener[key]
      if(!fns || fns.length ==0){
        return
      }
      for(let i = 0;i<fns.length;i++){
        fns[i].call(this,arguments)
      }
      
    }
  }

  

 

大致流程就是用数组缓存,事件名及对应的函数,然后触发事件时,遍历缓存列表,执行对应的函数。

eventsMixin(Vue) 这个方法就是将上面说的流程,添加到了Vue的原型上面,对外暴露了几个API,$on,$once,$off,#emit

$on方法,过滤掉了钩子函数中的方法。

$off方法,当事件是字符串时,将对应的回调设置为null,当为数组时,将对应的函数从缓存列表中删除。

$emit就不用说了,从缓存列表中找到对应的函数去执行。

源码如下:

  
export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        this.$on(event[i], fn)
      }
    } else {
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

  Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    function on () {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
  }

  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        this.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    const cbs = vm._events[event]
    if (!cbs) {
      return vm
    }
    if (arguments.length === 1) {
      vm._events[event] = null
      return vm
    }
    if (fn) {
      // specific handler
      let cb
      let i = cbs.length
      while (i--) {
        cb = cbs[i]
        if (cb === fn || cb.fn === fn) {
          cbs.splice(i, 1)
          break
        }
      }
    }
    return vm
  }

  Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      for (let i = 0, l = cbs.length; i < l; i++) {
        try {
          cbs[i].apply(vm, args)
        } catch (e) {
          handleError(e, vm, `event handler for "${event}"`)
        }
      }
    }
    return vm
  }
}
 

今日总结

开卷有益,书要多读,细度。勤于思考总结。

最后说两句

  1. 动一动您的小手,「点个赞吧」
  2. 都看到这里了,不妨 「加个关注」

javascript基础知识总结

原文地址:https://www.cnblogs.com/vali/p/14370553.html