简介
大家好,我是六六。今天分析关于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》
来源:oschina
链接:https://my.oschina.net/u/4361028/blog/3238379