zoukankan      html  css  js  c++  java
  • 《一天读一个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基础知识总结

  • 相关阅读:
    (转)STMFD和LDMFD指令个人理解分析
    [原创] linux下的串口调试工具
    详解Makefile 函数的语法与使用 (转)
    sed命令详解(转)
    [转]QT4解决中文乱码(tr的使用 | QTextCodec)
    新型的按键扫描程序,仅三行程序(转)
    VS2010旗舰版不能安装Silverlight4_Tools的解决方案
    ASP.NET MVC3+EF4+Oracle入门实例(六)
    一个ASP.NET文件下载类
    项目管理实践【四】自动编译和发布网站【Using Visual Studio with Source Control System to build and publish website automatically】
  • 原文地址:https://www.cnblogs.com/vali/p/14370553.html
Copyright © 2011-2022 走看看