深入浅出Vue实例事件方法(简单易学)

本秂侑毒 提交于 2020-04-17 14:05:25

【推荐阅读】微服务还能火多久?>>>

简介

大家好,我是六六。今天分析关于Vue实例事件相关的方法。与事件相关的实例方法有四个,vm.$on,vm.$emit,vm.$once,vm.$off。这四个方法都挂载到Vue的prototype属性上,接下里我们详细的讲一讲:

目录

  • vm.$on
  • vm.$once
  • vm.$off
  • vm.$emit
  • 总结

1. vm.$on

vm.$on( event, callback )

参数:

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

用法:

监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数。

示例:

vm.$on('test', function (msg) {
  console.log(msg)
})
vm.$emit('test', 'hi')
// => "hi"
复制代码

实现:

其实通过用法我们就知道,可以先把事件的回调函数先收集起来,等到事件触发时,我们从收集的回调函数取出来执行就好了。所以,我们就需要一个对象来存储这些回调函数。事实上,在执行new Vue()时,Vue会执行this._init进行一系列初始化工作,其中就会在实例上创建一个_events属性,来存储事件以及相应的回调函数。

vm._events=Object.create(null)
复制代码

知道了基本原理,我们手动实现一下:

Vue.prototype.$on=function(event,fn){
  const vm=this
  if(Array.isArray(fn)){
    for(let i=0,j=event.length;i<j;i++){
      this.$on(event,fn[i])
    }
  }else{
    (vm._events[event]||(vm.events[event]=[])).push(fn)
  }
  return vm
}
复制代码

解析:首先我们会先判断event参数是否为数组,然后遍历数组,再继续递归调用,之后将event作为属性名传入vm._events并且属性值为一个数组,接着就将回调函数push进去。等待被触发。

2. vm.$off

vm.$off( [event, callback] )

参数:

  • {string | Array} event (只在 2.2.2+ 支持数组)
  • {Function} [callback]

用法:

移除自定义事件监听器。

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

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

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

实现:

通过用法我们知道vm.$off是用于移除事件的,并且对于参数的不同有三种情况要分析。

2.1 无参数:移除所有事件

Vue.prototype.$off=function(event,fn){
 const vm=this
  if(arguments.length===0){
    vm._events=Object.create(null)
     return vm
  }
  return vm
}
复制代码

只要满足了arguments.length===0德情况,vm._events属性就会被重置,等于null.之前绑定的所有回调和事件都清楚了。

2.2 二个参数:移除该事件的监听器

Vue.prototype.$off=function(event,fn){
 const vm=this
  // 无参数
  if(!arguments){
    vm._events=Object.create(null)
     return vm
  }
  // 新增
  // 传递事件名和回调函数
  else if(fn && typeof event==='string'){
    const cbs=vm._events[event]
    let j=cbs.length
    for(let i=0;i<j;i++){
      // 
      if(fn===cbs[i]||cbs[i].fn===fn){
        cbs.splice(i,1)
        return 
      }
    }
  }
  return vm
}
复制代码

首先判断参数evnet为字符串和有回调函数,通过循环该事件的数组,找到该回调函数并使用splice方法移除。

2.3 一个参数:移除该事件的所有监听器

Vue.prototype.$off=function(event,fn){
   const vm=this
  // 无参数
  if(!arguments){
    vm._events=Object.create(null)
     return vm
  }
  // 新增
  // 一个参数
  else if(arguments.length===1){
    const e=arguments[0]
    //判断是否为数组
    if(Array.isArray(e)){
      for(let i=0,j=e.length;i<j;i++){
        this.$off(e[i])
      }
    }else{
      vm._events[event]=null
      return vm
    }
  }
  // 传递事件名和回调函数
  else if(fn && typeof event==='string'){
    const cbs=vm._events[event]
    let j=cbs.length
    for(let i=0;i<j;i++){
      // 
      if(fn===cbs[i]||cbs[i].fn===fn){
        cbs.splice(i,1)
        return 
      }
    }
  }
  return vm
}
复制代码

因为event参数可以为数组或者字符串,所以需要分情况讨论,是数组的话循环加递归就,找到该事件,指向null就移除了。

3. vm.$emit

vm.$emit( eventName, […args] )

参数:

  • { string} eventName
  • [...args]

用法:

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

实现:

我们知道所有事件监听的回调函数都存储在vm._events里面,那么我们只需要根据事件名找到他们,依次执行列表中的函调函数即可,并将参数传入回调函数。

Vue.prototype.$emit=function(event){
  const vm=this
  let cbs=vm._events[event]
  if(cbs){
    const args=toArray(arguments,1)
    for(let i=0,j=cbs.length;i<j;i++){
      try{
        cbs[i].apply(vm,args)
      }catch(e){
        console.log(e)
      }
    }
  }
  return vm
}
复制代码

其实也是很简单,toArray的作用就是将类似于数组的数据转换成真正的数组,第二个参数作为起始位置。

4. vm.$once

vm.$once( event, callback )

参数:

  • {string} event
  • {Function} callback

用法:

监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除。

实现:

vm.$once和 vm.$on的区别就是前者只能实现一次,所以我们就可以当自定义触发了执行拦截器,删除这个监听器并执行一次。

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
}
复制代码

我们在vm.$once中使用vm.$on来监听事件。首先我们是将on函数注册到事件中的(这点很重要,并不是fn).当自定义事件被触发时,我们会执行on函数,在这个函数里首先将自己移除掉,在执行fn.所以这个自定义事件就只执行一次。

问题:

我们仔细看一下代码,我们注册的是on函数,但是用户如果通过vm.$off移除的不是on函数啊,而是fn函数。但是fn函数我们没有注册啊,注册的是on函数(用户不知道on函数的存在),所以此时我们可以给on添加一个属性fn为fn这个函数on.fn=fn,在进行删除时,我们可以判断次函数的fn属性即可:

fn===cbs[i]||cbs[i].fn===fn
复制代码

on函数属性fn正是用户手写的函数,所以可以完美的解决了函数不相同的问题了。

5 总结

不想只做api的搬运工,想要更深入原理的去理解。如果本文有什么问题请大家及时提出来哦。

此文参考《深入浅出Vue.js》

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!