Vue事件API

众所周知,Vue.js为我们提供了四个事件API,分别是Vue事件机制 - 图1%EF%BC%8C%5B#card=math&code=on%5D%28https%3A%2F%2Fcn.vuejs.org%2Fv2%2Fapi%2F%23vm-on-event-callback%29%EF%BC%8C%5B)onceVue事件机制 - 图2%EF%BC%8C%5B#card=math&code=off%5D%28https%3A%2F%2Fcn.vuejs.org%2Fv2%2Fapi%2F%23vm-off-event-callback%29%EF%BC%8C%5B)emit

初始化事件

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

  1. {
  2. eventName: [func1, func2, func3]
  3. }

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

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

$on

Vue事件机制 - 图3emit触发。

  1. Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
  2. const vm: Component = this
  3. /*如果是数组的时候,则递归$on,为每一个成员都绑定上方法*/
  4. if (Array.isArray(event)) {
  5. for (let i = 0, l = event.length; i < l; i++) {
  6. this.$on(event[i], fn)
  7. }
  8. } else {
  9. (vm._events[event] || (vm._events[event] = [])).push(fn)
  10. // optimize hook:event cost by using a boolean flag marked at registration
  11. // instead of a hash lookup
  12. /*这里在注册事件的时候标记bool值也就是个标志位来表明存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/
  13. if (hookRE.test(event)) {
  14. vm._hasHookEvent = true
  15. }
  16. }
  17. return vm
  18. }

$once

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

  1. Vue.prototype.$once = function (event: string, fn: Function): Component {
  2. const vm: Component = this
  3. function on () {
  4. /*在第一次执行的时候将该事件销毁*/
  5. vm.$off(event, on)
  6. /*执行注册的方法*/
  7. fn.apply(vm, arguments)
  8. }
  9. on.fn = fn
  10. vm.$on(event, on)
  11. return vm
  12. }

$off

$off用来移除自定义事件

  1. Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
  2. const vm: Component = this
  3. // all
  4. /*如果不传参数则注销所有事件*/
  5. if (!arguments.length) {
  6. vm._events = Object.create(null)
  7. return vm
  8. }
  9. // array of events
  10. /*如果event是数组则递归注销事件*/
  11. if (Array.isArray(event)) {
  12. for (let i = 0, l = event.length; i < l; i++) {
  13. this.$off(event[i], fn)
  14. }
  15. return vm
  16. }
  17. // specific event
  18. const cbs = vm._events[event]
  19. /*Github:https://github.com/answershuto*/
  20. /*本身不存在该事件则直接返回*/
  21. if (!cbs) {
  22. return vm
  23. }
  24. /*如果只传了event参数则注销该event方法下的所有方法*/
  25. if (arguments.length === 1) {
  26. vm._events[event] = null
  27. return vm
  28. }
  29. // specific handler
  30. /*遍历寻找对应方法并删除*/
  31. let cb
  32. let i = cbs.length
  33. while (i--) {
  34. cb = cbs[i]
  35. if (cb === fn || cb.fn === fn) {
  36. cbs.splice(i, 1)
  37. break
  38. }
  39. }
  40. return vm
  41. }

$emit

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

  1. Vue.prototype.$emit = function (event: string): Component {
  2. const vm: Component = this
  3. if (process.env.NODE_ENV !== 'production') {
  4. const lowerCaseEvent = event.toLowerCase()
  5. if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
  6. tip(
  7. `Event "${lowerCaseEvent}" is emitted in component ` +
  8. `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
  9. `Note that HTML attributes are case-insensitive and you cannot use ` +
  10. `v-on to listen to camelCase events when using in-DOM templates. ` +
  11. `You should probably use "${hyphenate(event)}" instead of "${event}".`
  12. )
  13. }
  14. }
  15. let cbs = vm._events[event]
  16. if (cbs) {
  17. /*将类数组的对象转换成数组*/
  18. cbs = cbs.length > 1 ? toArray(cbs) : cbs
  19. const args = toArray(arguments, 1)
  20. /*遍历执行*/
  21. for (let i = 0, l = cbs.length; i < l; i++) {
  22. cbs[i].apply(vm, args)
  23. }
  24. }
  25. return vm
  26. }