Vue 进阶系列丨事件相关的实例方法

Vue 进阶系列教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!


2013年7月28日,尤雨溪第一次在 GItHub 上为 Vue.js 提交代码;2015年10月26日,Vue.js 1.0.0版本发布;2016年10月1日,Vue.js 2.0发布。

最早的 Vue.js 只做视图层,没有路由, 没有状态管理,也没有官方的构建工具,只有一个库,放到网页里就可以直接用了。

后来,Vue.js 慢慢开始加入了一些官方的辅助工具,比如路由(Router)、状态管理方案(Vuex)和构建工具(Vue-cli)等。此时,Vue.js 的定位是:The Progressive Framework。翻译成中文,就是渐进式框架。

Vue.js2.0 引入了很多特性,比如虚拟 DOM,支持 JSX 和 TypeScript,支持流式服务端渲染,提供了跨平台的能力等。Vue.js 在国内的用户有阿里巴巴、百度、腾讯、新浪、网易、滴滴出行、360、美团等等。

Vue 已是一名前端工程师必备的技能,现在就让我们开始深入学习 Vue.js 内部的核心技术原理吧!


什么是实例方法

在 Vue.js 的内部有这样一段代码:

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'


function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}


initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)


export default Vue

上方代码定义了一个类,名字叫做 Vue,然后在创建实例时,调用 _init 方法,执行一系列初始化操作。并且调用了 initMixin、stateMixin、eventMixin、lifecycleMixin 和 renderMixin 方法,向 Vue 构造函数的原型上挂载方法,也就是这里的实例方法。

  • initMixin:用来初始化操作的,比如整个生命周期的流程以及响应式系统流程的启动

  • stateMixin:向Vue原型上挂载数据相关的实例方法,vm.$watch、vm.$set、vm.$delete

  • eventsMixin:向Vue原型上挂载事件相关的实例方法,vm.$on、vm.$once、vm.$off 和 vm.$emit

  • lifecycleMixin:向Vue原型上挂载生命周期相关的实例方法,vm.$forceUpdate 和 vm.$destory

  • renderMixin:向Vue原型上挂载生命周期相关的实例方法,vm.$nextTick

本篇文章我们着重介绍事件相关的实例方法,其他实例方法将陆续推出。


什么是 vm

可能有人疑惑,什么是 vm?这里粘贴 Vue 部分源码来解释:

Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = 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)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
}

上方代码是 eventsMixin 函数中定义 vm.$on 的源码部分,从第二行我们可以看到,vm 其实就是 this 的别名,那么这里为什么要这么定义呢?是为了保证后续代码的 this 指向正确。

所以大家不必要纠结 vm 到底是什么,其实所有的实例方法都是通过Vue.prototype.$on = function(){} 的形式,向 Vue 的原型上挂载的,在实例执行$on方法时,会向原型链上寻找 $on。

$on 也是一个函数名字而已,其实也可以叫 helloword,像Vue.prototype.helloworld = function(){} 这样,就是向 Vue 的原型上挂载了helloworld方法。


vm.$on

vm.$on 用来监听当前实例上的自定义事件,也就是注册一个监听事件回调函数,每当监听到有人调用这个事件的时候,注册的回调就会被调用。调用事件就要用到 vm.$emit 函数。执行效果如下:

vm.$on('hfun',function(arg){
  console.log(arg)
})  // 注册了'hfun'事件以及对应的回调函数


vm.$emit('hfun','hahaha')
// hahaha   

上方代码的 hahaha 输出内容,是 vm.$on 监听的回调函数执行输出的内容。

事件实现逻辑是这样的,在注册事件时将事件回调函数收集起来,在触发事件时将收集起来的回调函数依次调用。当 event 参数为数组时,遍历数组,将其中的每一项递归调用 vm.$on,使回调可以被注册到数组中每项事件名所指向的事件列表中,也就是说,每个事件名,可能对应多个事件,每次触发当前事件,会导致多个回调函数同时触发。

如果不是数组,我们就将当前回调存储起来。vm._events 是一个对象,用来存储事件的,是我们在执行 new Vue() 时,执行 this._init 方法时进行的初始化操作,在 vue 的实例上创建的一个 _events 属性。

在代码中,我们使用事件名作为对象的属性,取出当前事件对应的事件列表,如果列表不存在,就使用空数组初始化,然后再将回调函数添加到事件列表中。


vm.$off

vm.$off 用来移除自定义事件监听器。

  • 如果没有提供参数,则移除所有的事件监听器。

  • 如果只提供了事件,则移除当前事件的所有监听器。

  • 如果既提供了事件,又提供了回调函数,则只移除这个事件的这个监听器。

(1)第一种,没有提供参数情况:

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
    }
}


上面是 vue 关于 vm.$off 没有参数的情况对应的源码部分,首先判断参数长度是否为零,如果没有参数,我们直接 vm._events 属性重置为空,就等同于将所有事件都清楚了。

(2)第二种,提供一个参数情况:

   首先,当第一个参数是一个数组时,我们就遍历这个数组,并且对每一项都执行 vm.$off,这样就对数组内所有的事件都清除了。

Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // array of events
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
}

    其次,如果不是数组的话,我们就移除该事件所有的监听器,我们只需要将 vm._events[event] 重置为空即可。

Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // specific event
    const cbs = vm._events[event]
    if (!cbs) {  // 先判断这个事件有没有被监听,没有的话直接退出
      return vm
    }
    if (!fn) {   // 有被监听,直接将对应的事件列表清空即可
      vm._events[event] = null
      return vm
    }
}

(3)第三种,既提供了事件,又提供了回调函数:

既提供了事件,又提供了回调函数,我们要只需要使用参数中提供的事件名,从 vm._events 中去除事件列表,然后遍历事件列表,找到和提供的回调函数一样的那个,将他从事件列表中移除即可。

Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // 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
}

vm.$once

vm.$once 用来监听一个自定义事件,但是只触发一次,在第一次触发之后就移除监听器。我们可以结合 vm.$on 和 vm.$off 实现,当自定义事件触发后,将监听器从事件列表中移除。

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
}

vm.$emit

vm.$emit 用来触发事件,附加的参数会传递给事件监听器回调。我们只需要从 vm._events 中取出对应的事件列表,然后依次执行列表中的监听器回调函数即可(前面我们说过,一个事件可能对应不止一个回调函数)。

Vue.prototype.$emit = function (event: string): Component {
  const vm: Component = this
  let cbs = vm._events[event]
  if (cbs) {
    const args = toArray(arguments, 1)
    for (let i = 0, l = cbs.length; i < l; i++) {
      cb[i].apply(vm,args)
    }
  }
  return vm
}

Vue 进阶系列教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!

叶阳辉

HFun 前端攻城狮

往期精彩:

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值