V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
answershuto
V2EX  ›  前端开发

Vue.js 源码——事件机制

  •  
  •   answershuto ·
    answershuto · 2017-09-27 19:34:24 +08:00 · 1988 次点击
    这是一个创建于 2402 天前的主题,其中的信息可能已经有所发展或是发生改变。

    写在前面

    因为对 Vue.js 很感兴趣,而且平时工作的技术栈也是 Vue.js ,这几个月花了些时间研究学习了一下 Vue.js 源码,并做了总结与输出。 文章的原地址:https://github.com/answershuto/learnVue。 在学习过程中,为 Vue 加上了中文的注释https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以对其他想学习 Vue 源码的小伙伴有所帮助。 可能会有理解存在偏差的地方,欢迎提 issue 指出,共同学习,共同进步。

    Vue 事件 API

    众所周知,Vue.js 为我们提供了四个事件 API,分别是$on$once$off,[$emit]( https://cn.vuejs.org/v2/api/#vm-emit-event-… args)。

    初始化事件

    初始化事件在 vm 上创建一个_events 对象,用来存放事件。_events 的内容如下:

    {
        eventName: [func1, func2, func3]
    }
    

    存放事件名以及对应执行方法。

    /*初始化事件*/
    export function initEvents (vm: Component) {
      /*在 vm 上创建一个_events 对象,用来存放事件。*/
      vm._events = Object.create(null)
      /*这个 bool 标志位来表明是否存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/
      vm._hasHookEvent = false
      // init parent attached events
      /*初始化父组件 attach 的事件*/
      const listeners = vm.$options._parentListeners
      if (listeners) {
        updateComponentListeners(vm, listeners)
      }
    }
    

    $on

    $on 方法用来在 vm 实例上监听一个自定义事件,该事件可用$emit 触发。

      Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
        const vm: Component = this
    
        /*如果是数组的时候,则递归$on,为每一个成员都绑定上方法*/
        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
          /*这里在注册事件的时候标记 bool 值也就是个标志位来表明存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/
          if (hookRE.test(event)) {
            vm._hasHookEvent = true
          }
        }
        return vm
      }
    

    $once

    $once 监听一个只能触发一次的事件,在触发以后会自动移除该事件。

      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
      }
    

    $off

    $off 用来移除自定义事件

    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
        /*如果 event 是数组则递归注销事件*/
        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]
        /*Github:https://github.com/answershuto*/
        /*本身不存在该事件则直接返回*/
        if (!cbs) {
          return vm
        }
        /*如果只传了 event 参数则注销该 event 方法下的所有方法*/
        if (arguments.length === 1) {
          vm._events[event] = null
          return vm
        }
        // 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
      }
    

    $emit

    $emit 用来触发指定的自定义事件。

    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++) {
            cbs[i].apply(vm, args)
          }
        }
        return vm
      }
    

    关于

    作者:染陌

    Email: [email protected] or [email protected]

    Github: https://github.com/answershuto

    Blog:http://answershuto.github.io/

    知乎主页:https://www.zhihu.com/people/cao-yang-49/activities

    知乎专栏:https://zhuanlan.zhihu.com/ranmo

    掘金: https://juejin.im/user/58f87ae844d9040069ca7507

    osChina:https://my.oschina.net/u/3161824/blog

    转载请注明出处,谢谢。

    欢迎关注我的公众号

    3 条回复    2017-09-28 00:30:16 +08:00
    blanu
        1
    blanu  
       2017-09-27 22:34:39 +08:00 via iPhone
    我觉得这是非常无聊的文章
    fuermosi777
        2
    fuermosi777  
       2017-09-28 00:06:49 +08:00
    我同意一楼的看法
    Lisp
        3
    Lisp  
       2017-09-28 00:30:16 +08:00 via iPhone
    下拉最后,并不是好心的分享
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1001 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 19:41 · PVG 03:41 · LAX 12:41 · JFK 15:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.