Vue.js源码——事件机制

Posted 染陌的技术空间

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue.js源码——事件机制相关的知识,希望对你有一定的参考价值。

## 写在前面


因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出。

在学习过程中,为Vue加上了中文的注释[https://github.com/answershuto/learnVue/tree/master/vue-src](https://github.com/answershuto/learnVue/tree/master/vue-src),希望可以对其他想学习Vue源码的小伙伴有所帮助。

可能会有理解存在偏差的地方,欢迎提issue指出,共同学习,共同进步。



## Vue事件API


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


## 初始化事件


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

```json

{

    eventName: [func1, func2, func3]

}

```

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



```javascript

/*初始化事件*/

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触发。


```javascript

  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监听一个只能触发一次的事件,在触发以后会自动移除该事件。


```javascript

  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用来移除自定义事件


```javascript

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用来触发指定的自定义事件。


```javascript

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

  }

```


## 关于




Github:  [https://github.com/answershuto](https://github.com/answershuto)


Blog:[http://answershuto.github.io/](http://answershuto.github.io/)


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


知乎专栏:[https://zhuanlan.zhihu.com/ranmo](https://zhuanlan.zhihu.com/ranmo)


掘金: [https://juejin.im/user/58f87ae844d9040069ca7507](https://juejin.im/user/58f87ae844d9040069ca7507)


osChina:[https://my.oschina.net/u/3161824/blog](https://my.oschina.net/u/3161824/blog)


转载请注明出处,谢谢。



![](https://user-gold-cdn.xitu.io/2017/9/24/99de30e7ef948aabf132e91d543807a0)


以上是关于Vue.js源码——事件机制的主要内容,如果未能解决你的问题,请参考以下文章

Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 四 | View 事件传递机制 )

Spring事件监听机制源码解析

Android 事件分发机制分析及源码详解

Android 事件分发机制分析及源码详解

Android 源码解析View的touch事件分发机制

redis源码学习事件机制