资讯专栏INFORMATION COLUMN

Vue实例方法之事件的实现

SwordFly / 3035人阅读

摘要:例子触发自定义事件二源码分析事件的初始化工作我们在使用自定义事件的的时候,肯定有个地方是需要来存我们的事件和回调的地方。这里不做过多讨论自定义事件的挂载方式自定义事件的挂载是在方法中实现的。

开始

这段时间一直在看vue的源码,源码非常多和杂,所以自己结合资料和理解理出了一个主线,然后根据主线去剥离其他的一些知识点,然后将各个知识点逐一学习。这里主要是分析的事件系统的实现。

正文 一、了解使用方式

在分析之前先了解下几个api的使用方式:

vm.$on(event, callback)

参数

{string | Array} event (数组只在 2.2.0+ 中支持)

{Function} callback

用法$on事件需要两个参数,一个是监听的当前实例上的事件名,一个是事件触发的回调函数,回调函数接受的是在事件出发的时候额外传递的参数。

例子:

vm.$on("test", function (msg) {
  console.log(msg)
})
vm.$emit("test", "hi")
// => "hi"
vm.$once(event, callback)

$once事件整体上来说和$on事件的使用方式差不多,但是event只支持字符串也就是说只支持单个事件。并且该事件再触发一次后就移除了监听器。

例子

vm.$once("testonce", function (msg) {
  console.log(msg)
})
vm.$off([event, callback])

参数

{string | Array} event(仅在 2.2.2+ 支持数组)

{Function} [callback]

用法:移除自定义事件监听器

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

如果只提供了事件,则移除该事件所有的监听器;

如果同时提供了事件与回调,则只移除这个回调的监听器。

例子

vm.$off()
vm.$off("test")
vm.$off("test1", function (msg) {
  console.log(msg)
})
vm.$off(["test1","test2"], function (msg) {
  console.log(msg)
})
vm.$emit(event, [..args])

参数

{string} event 要触发的事件名

[...args]可选

用法

触发当前实例上的事件。附加参数都会传给监听器回调。

例子

vm.$emit("test", "触发自定义事件")
二、源码分析
事件的初始化工作

我们在使用自定义事件的api的时候,肯定有个地方是需要来存我们的事件和回调的地方。在vue中大部分的初始化工作都是在core/instance/init.jsinitMixin方法中。所以我们能够在initMixin看到initEvents方法。

// initEvents
export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

上面的代码可以看到,在初始化Vue事件的时候,在vm实例上面挂载了一个_events的空对象。后面我们自己调用的自定义事件都存在里面。

因为vue本身在组件嵌套的时候就有子组件使用父组件的事件的时候。所以就可以通过updateComponentListeners方法把父组件事件监听器(比如click)传递给子组件。(这里不做过多讨论)

自定义事件的挂载方式

自定义事件的挂载是在eventsMixin方法中实现的。这里面将四个方法挂在Vue的原型上面。

Vue.prototype.$on
Vue.prototype.$once
Vue.prototype.$off
Vue.prototype.$emit
Vue.prototype.$on的实现
Vue.prototype.$on = function (event: string | Array, fn: Function): Component {
const vm: Component = this
    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
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
}

第一个参数就是自定义事件,因为可能是数组,所以判断如果是数组,那么就循环调用this.$on方法。
如果不是数组,那么就直接向最开始定义的_events对象集合里面添加自定义事件。

所以这个时候_events对象生成的格式大概就是下面:

vm._events={
    "test":[fn,fn...],
    "test1":[fn,fn...]
}
Vue.prototype.$once 的实现
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
}

这里定义了一个on函数。接着把fn赋值给on.fn。最后在调用的是vm.$on。这里传入的就是事件名和前面定义的on函数。on函数在执行的时候会先移除_events中对应的事件,然后调用fn

所以分析下得到的是:

vm._events={
    "oncetest":[ 
          function on(){
              vm.$off(event,on)
              fn.apply(vm,arguments)
          } ,
          ...
     ]
}
Vue.prototype.$off的实现
Vue.prototype.$off = function (event?: string | Array, fn?: Function): Component {
    const vm: Component = this
    // all
    // 如果没有传任何参数的时候,直接清楚所有挂在_events对象上的所有事件。
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    // 如果第一个参数是数组的话,那么就循环调用this.$off方法
    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]
    // 没有相关的事件的时候直接返回vm实例
    if (!cbs) {
      return vm
    }
    // 如果只传入了事件名,那么清除该事件名下所有的事件。 也就是说 vm._events = {"test": null, ...}
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    // 如果传入的第二个参数为真,那么就去cbs里面遍历,在cbs中找到和fn相等的函数,然后通过splice删除该函数。
    if (fn) {
      // 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
}

上面主要就是实现的下面三种情况:

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

如果只提供了事件,则移除该事件所有的监听器;

如果同时提供了事件与回调,则只移除这个回调的监听器。

Vue.prototype.$emit 的实现
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}".`
        )
      }
    }
    // 匹配到事件列表,该列表是一个json。
    let cbs = vm._events[event]
    if (cbs) {
      //将该json转化成为真正的数组
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      // 循环遍历调用所有的自定义事件。
      for (let i = 0, l = cbs.length; i < l; i++) {
        try {
          cbs[i].apply(vm, args)
        } catch (e) {
          handleError(e, vm, `event handler for "${event}"`)
        }
      }
    }
    return vm
}

上面主要意思是:匹配到json中相关key值的value,这个value先转换成真正的数组,再循环遍历数组,传入给的参数执行数组中的每个函数

最后

vue中的自定义事件主要目的是为了组件之间的通信。因为_events对象是挂在Vue实例上的。因此每个组件是都可以访问到vm._events的值的,也能够向其中push值的。

整个自定义事件系统呢就是在vm实例上挂载一个_events的对象,可以理解为一个json,其中json的key值就是自定义事件的名称,一个key值可能对应着多个自定义事件,因此json中每个key对应的value都是一个数组,每次执行事件监听都会向数组中push相关的函数,最终通过$emit函数传入的参数,匹配到json中相应的key,val值,从而使用给定的参数执行数组中的函数

最后的_events对象:

vm._events={
    "test1":[fn,fn,fn],
    "test2":[fn],
    "oncetest":[ 
          function on(){
              vm.$off(event,on)
              fn.apply(vm,arguments)
          },
          ... 
     ],
     ...
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/52328.html

相关文章

  • Vue实例方法事件实现

    摘要:例子触发自定义事件二源码分析事件的初始化工作我们在使用自定义事件的的时候,肯定有个地方是需要来存我们的事件和回调的地方。这里不做过多讨论自定义事件的挂载方式自定义事件的挂载是在方法中实现的。 开始 这段时间一直在看vue的源码,源码非常多和杂,所以自己结合资料和理解理出了一个主线,然后根据主线去剥离其他的一些知识点,然后将各个知识点逐一学习。这里主要是分析的事件系统的实现。 正文 一、...

    刘德刚 评论0 收藏0
  • Vue原理】Event - 源码版 绑定组件自定义事件

    摘要:写文章不容易,点个赞呗兄弟专注源码分享,文章分为白话版和源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于版本如果你觉得排版难看,请点击下面链接或者拉到下面关注公众号也可以吧原理源码版之绑定组件自定义事件组件 写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于...

    amuqiao 评论0 收藏0
  • Vue.js起手式+Vue小作品实战

    摘要:本文是小羊根据文档进行解读的第一篇文章,主要内容涵盖的基础部分的知识的,文章顺序基本按照官方文档的顺序,每个知识点现附上代码,然后根据代码给予个人的一些理解,最后还放上在线编辑的代码以供练习和测试之用在最后,我参考上的一篇技博,对进行初入的 本文是小羊根据Vue.js文档进行解读的第一篇文章,主要内容涵盖Vue.js的基础部分的知识的,文章顺序基本按照官方文档的顺序,每个知识点现附上代...

    CompileYouth 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<