Vue events fall into two categories: native DOM events and custom events. Where native DOM events can be used on both elements and components, where the. Native modifier is added. Vue handles native DOM events bound to elements and components by calling native apis, while custom events on components are handled by a publish/subscribe event-centric mechanism.

Implement event center manually

Implementing an event center that follows a publish/subscribe pattern is relatively simple, with code like this:

function EventEmitter() {
  this._events = Object.create(null)
}

EventEmitter.prototype.$on = function(type, handler) {(this._events[type] || (this._events[type] = [])).push(handler)
}

EventEmitter.prototype.$off = function(type, handler) {
  var events = this._events[type]
  if (events) {
    var cb
    var i = events.length
    while (i--) {
      cb = events[i]
      if (cb === handler) {
        events.splice(i, 1)
        break
      }
    }
  }
}

EventEmitter.prototype.$emit = function(type) {
  var args = toArray(arguments.1)
  if (this._events[type]) {
    this._events[type].forEach(fn= > fn.apply(this, args))
  }
}

EventEmitter.prototype.$once = function(type, handler) {
  var _this = this
  var flag = false

  function on() {
    _this.$off(type, on)
    if(! flag) { flag =true
      handler.apply(_this, arguments)
    }
  }

  _this.$on(type, on)
}

export default EventEmitter
Copy the code

The subscription methods are stored in the _events property of the instance object. The name of the custom event is the _events property, whose value is an array type that stores the callback function after the event is triggered. Subscribe to events by calling the $on method by pushing the callback function into the corresponding property array on the _events object. The custom event listener is removed by calling the $off method by removing the callback function from the corresponding property array on the _events object. The corresponding event is triggered by calling the $emit method, essentially taking out the corresponding subscription method and executing it. Add a custom event that fires only once by calling the $once method, using an identity variable to determine whether the method fires.

Ii. Vue Event Center

When we talked about V-ON directives in the previous article, the section on using custom directives on components did not explain their implementation. The custom event is added and removed by calling the instance method $ON and $off. The custom event is triggered by calling the instance method $emit. The constructor of a Vue object is called when it is instantiated:

function Vue (options) {
  /* omit warning message */
  this._init(options)
}
/* omit other mixins */
eventsMixin(Vue)

Vue.prototype._init = function (options) {
  const vm = this
  / * omit... * /
  initEvents(vm)
}
Copy the code

The object that holds the subscription method is defined in the initEvents method, and the instance event method is defined in eventsMixin.

function initEvents (vm) {
  vm._events = Object.create(null)
  / * omit... * /
}

function eventsMixin (Vue) {
  const hookRE = /^hook:/
  Vue.prototype.$on=function(event,fn){/ * * / has been eliminated}

  Vue.prototype.$once=function(event,fn){/ * * / has been eliminated}

  Vue.prototype.$off=function(event,fn){/ * * / has been eliminated}

  Vue.prototype.$emit=function(event){/ * * / has been eliminated}}Copy the code

Let’s start with the implementation of $on:

Vue.prototype.$on = function(event,fn){
  const vm = this
  if (Array.isArray(event)) {
    for (let i = 0, l = event.length; i < l; i++) {
      vm.$on(event[i], fn)
    }
  } else {
    (vm._events[event] || (vm._events[event] = [])).push(fn)
    if (hookRE.test(event)) {
      vm._hasHookEvent = true}}return vm
}
Copy the code

$on = array (); $on = array (); $on = array (); $on = array (); If the first argument event matches the /^hook:/ re, the _hasHookEvent property of the instance object is set to true. This is related to the lifecycle hook function, which will be explained in the next article, Lifecycle.

Vue.prototype.$off=function(event,fn){
  const vm = this

  if (!arguments.length) {
    vm._events = Object.create(null)
    return vm
  }

  if (Array.isArray(event)) {
    for (let i = 0, l = event.length; i < l; i++) {
      vm.$off(event[i], fn)
    }
    return vm
  }

  const cbs = vm._events[event]
  if(! cbs) {return vm }
  if(! fn) { vm._events[event] =null
    return vm
  }

  let cb
  let i = cbs.length
  while (i--) {
    cb = cbs[i]
    if (cb === fn || cb.fn === fn) {
      cbs.splice(i, 1)
      break}}return vm
}
Copy the code

A number of boundary conditions are handled in the $off method, such as calling the method with no arguments and emptying the vm._events variable, which removes all event listeners. If the first argument is an array, the $off method is looped; If only the first parameter is provided, vm._events[event] is null, that is, all listeners for the event are removed. As you can see, there is one more condition in the core implementation of the $off method than we implemented manually: cb.fn === fn. The fn attribute of the event performs the same deletion as the method to be deleted. This condition is added to match the implementation of the $once method.

Vue.prototype.$once=function(event, fn){
  const vm = this
  function on () {
    vm.$off(event, on)
    fn.apply(vm, arguments)
  }
  on.fn = fn
  vm.$on(event, on)
  return vm
}
Copy the code

The $ONCE method is implemented by calling the $ON method, but the callback is wrapped in the inner function on, in the trigger $ON method will call the inner function of the $OFF method to remove the event listening, so as to realize the $once method to listen for a custom event, but only trigger a function.

Vue.prototype.$emit=function(event){
  const vm = this
  /* omit warning message */
  let cbs = vm._events[event]
  if (cbs) {
    cbs = cbs.length > 1 ? toArray(cbs) : cbs
    const args = toArray(arguments.1)
    const info = `event handler for "${event}"`
    for (let i = 0, l = cbs.length; i < l; i++) {
      invokeWithErrorHandling(cbs[i], vm, args, vm, info)
    }
  }
  return vm
}
Copy the code

The $emit method is implemented manually as in the previous section, executing the corresponding method stored in _events, except that Vue does some error handling by calling invokeWithErrorHandling.

Third, EventBus

EventBus is an EventBus that can easily communicate between non-parent and child components. EventBus is implemented in Vue through the event center mechanism.

// event-bus.js
import Vue from 'vue'
const bus = new Vue()
export default bus

/ / A component
import bus from '@/event-bus.js'
bus.$on('CONSOLE', number => {
  console.log(number)
})

/ / B component
import bus from '@/event-bus.js'
bus.$emit('CONSOLE'.1)

/ / C components
import bus from '@/event-bus.js'
bus.$off('CONSOLE')
Copy the code

EventBus is simple to implement, easy to operate and flexible. However, because it is too flexible, if used casually in the project, it will be a disaster to maintain later. If there are many places where communication between non-parent and child components is required, the most correct choice is to use VUex for state management. The principle of VUEX will be explained in a subsequent article.

Four,

The processing of custom events in Vue is realized through the event center mechanism based on publish/subscribe mode. The core idea is to store events in an attribute object of the instance, and events are added, deleted, triggered and other operations are carried out on this object.

Welcome to pay attention to the public number: front-end Taohuayuan, mutual exchange and learning!