Отправка событий парсинга исходного кода Vue ($on, $emit, $once, $off)

Vue.js

В этой статье будут проанализированы четыре метода, связанные с отправкой событий в экземпляре vue, а именно:

  • $on: слушать события
  • $off: удалить событие слушателя
  • $emit: вызвать событие
  • $once: слушать события, слушать только один раз

Прежде всего, обычно вводятся основные принципы этих четырех методов:

1. В экземпляре vue будет создан объект для сохранения всех отслеживаемых событий: vm._events = {}

2. Всякий раз, когда мы хотим прослушать событие, добавляем пару ключ-значение в VM._Events, имя события в качестве ключа, пустой массив в качестве значения. Например, название события, которое мы должны прослушать.event1,ноvm._events = {event1: []}

3. В соответствующий массив будут добавлены функции обратного вызова, которые слушают события, например, мы вызываем

vm.$on('event1', cb1);
vm.$on('event1', cb2);
vm.$on('event1', cb3);

тогда в это времяvm._events={event1: [cb1, cb2, cb3]}

4. Когда вызывается метод удаления события прослушивателя, выполняемая операция заключается в удалении функции обратного вызова в соответствующем массиве, например, когда мы вызываем

vm.$off('event1', cb1);

В это время cb1 убирается,vm._events={event1: [cb2, cb3]}

5. Когда мы выполняем$emitКогда соответствующее событие срабатывает, операция состоит в том, чтобы извлечь функцию обратного вызова из соответствующего массива события и выполнить ее.Например, когда мы вызываем

vm.$emit('event')

Это будет вывезеноevent1соответствующий массивуcb2а такжеcb3воплощать в жизнь

6.$onceУказывает на то, что событие будет запущено и выполнено только один раз, и его будет бесполезно запускать позже, например, когда мы вызываем:

// 用$on方法监听event2,回调函数为cb4
vm.$on('event2', cb4);

// 用$once方法监听event2,回调函数为cb5
vm.$once('event2', cb5);

// 触发event2事件,会执行cb4和cb5
vm.$emit('event2');

// 再次触发event2事件,这里只会执行cb4,不会执行cb5,cb5只会执行一次
vm.$emit('event2');

Вышеизложенное является основным принципом метода диспетчеризации событий.Конечно, есть несколько более сложных способов использования этих методов, таких как

  • $on(['event1', 'event2'], cb) // прослушивание нескольких событий
  • $off(['event1', 'event2'], cb) // удалить несколько событий
  • $off() // удалить событие без передачи параметров
  • $off('event1') // удалить событие и передать параметр
  • $off('event1', cb) // удалить событие, передающее два параметра
  • $emit('event1', param1, param2) // запускаем событие для передачи параметров

После прочтения приведенного ниже анализа исходного кода вы будете знать, как действовать в таких ситуациях.

Давайте посмотрим на конкретную реализацию vue source в этих четырех методах:

$on:

  Vue.prototype.$on = function (event, fn) {
    const vm = this
    
    // 我们传入的要监听的事件可能为数组,这时候对数组里的每个事件再递归调用$on方法
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
    
      // 之前已经有监听event事件,则将此次监听的回调函数添加到其数组中,否则创建一个新数组并添加fn
      (vm._events[event] || (vm._events[event] = [])).push(fn)
    }
    return vm
  }

$off:

  Vue.prototype.$off = function (event, fn) {
    const vm = this
    // all
    if (!arguments.length) {
    // 如果没有传参数,则清空所有事件的监听函数
      vm._events = Object.create(null)
      return vm
    }
    
    // 如果传的event是数组,则对该数组里的每个事件再递归调用$off方法
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    
    // 获取当前event里所有的回调函数
    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
  }

$emit:

Vue.prototype.$emit = function (event) {
    const vm = this
    
    // 拿出触发事件对应的回调函数列表
    let cbs = vm._events[event]
    
    if (cbs) {
      
      // $emit方法可以传参,这些参数会在调用回调函数的时候传进去
      const args = toArray(arguments, 1)
      
      // 遍历回调函数列表,调用每个回调函数
      for (let i = 0, l = cbs.length; i < l; i++) {
        cbs[i].apply(vm, args)
      }
    }
    return vm
  }
}

$once:

  Vue.prototype.$once = function (event, fn) {
    const vm = this
    
    // 封装一个高阶函数on,在on里面调用fn
    function on () {
      // 每当执行了一次on,移除event里的on事件,后面再触发event事件就不会再执行on事件了,也就不会执行on里面的fn事件
      vm.$off(event, on)
      
      // 执行on的时候,执行fn函数
      fn.apply(vm, arguments)
    }
    
    // 这个赋值是在$off方法里会用到的
    // 比如我们调用了vm.$off(fn)来移除fn回调函数,然而我们在调用$once的时候,实际执行的是vm.$on(event, on)
    // 所以在event的回调函数数组里添加的是on函数,这个时候要移除fn,我们无法在回调函数数组里面找到fn函数移除,只能找到on函数
    // 我们可以通过on.fn === fn来判断这种情况,并在回调函数数组里移除on函数
    on.fn = fn
    
    // $once最终调用的是$on,并且回调函数是on
    vm.$on(event, on)
    return vm
  }