个人网站号备案吗,聊天室网站开发,网站建设 腾云,无锡网站建设唯唯网络目录 0.前言
1. vm.$on
1.1 用法回顾
1.2 内部原理
2. vm.$emit
2.1 用法回顾
2.2 内部原理
3. vm.$off
3.1 用法回顾
3.2 内部原理
4. vm.$once
4.1 用法回顾
4.2 内部原理 0.前言
与事件相关的实例方法有4个#xff0c;分别是vm.$on、vm.$emit、vm.$off和vm.$o…目录 0.前言
1. vm.$on
1.1 用法回顾
1.2 内部原理
2. vm.$emit
2.1 用法回顾
2.2 内部原理
3. vm.$off
3.1 用法回顾
3.2 内部原理
4. vm.$once
4.1 用法回顾
4.2 内部原理 0.前言
与事件相关的实例方法有4个分别是vm.$on、vm.$emit、vm.$off和vm.$once。它们是在eventsMixin函数中挂载到Vue原型上的代码如下
export function eventsMixin (Vue) {Vue.prototype.$on function (event, fn) {}Vue.prototype.$once function (event, fn) {}Vue.prototype.$off function (event, fn) {}Vue.prototype.$emit function (event) {}
}当执行eventsMixin函数后会向Vue原型上挂载上述4个实例方法。
接下来我们就来分析这4个与事件相关的实例方法其内部的原理都是怎样的。
1. vm.$on
1.1 用法回顾
在介绍方法的内部原理之前我们先根据官方文档示例回顾一下它的用法。
vm.$on( event, callback )参数 {string | Arraystring} event (数组只在 2.2.0 中支持){Function} callback 作用 监听当前实例上的自定义事件。事件可以由vm.$emit触发。回调函数会接收所有传入事件触发函数的额外参数。 示例 vm.$on(test, function (msg) {console.log(msg)
})
vm.$emit(test, hi)
// hi1.2 内部原理
在介绍内部原理之前我们先有一个这样的概念$on和$emit这两个方法的内部原理是设计模式中最典型的发布订阅模式首先定义一个事件中心通过$on订阅事件将事件存储在事件中心里面然后通过$emit触发事件中心里面存储的订阅事件。
OK有了这个概念之后接下来我们就先来看看$on方法的内部原理。该方法的定义位于源码的src/core/instance/event.js中如下
Vue.prototype.$on function (event, fn) {const vm: Component thisif (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)}return vm
}$on方法接收两个参数第一个参数是订阅的事件名可以是数组表示订阅多个事件。第二个参数是回调函数当触发所订阅的事件时会执行该回调函数。
首先判断传入的事件名是否是一个数组如果是数组就表示需要一次性订阅多个事件就遍历该数组将数组中的每一个事件都递归调用$on方法将其作为单个事件订阅。如下
if (Array.isArray(event)) {for (let i 0, l event.length; i l; i) {this.$on(event[i], fn)}
}如果不是数组那就当做单个事件名来处理以该事件名作为key先尝试在当前实例的_events属性中获取其对应的事件列表如果获取不到就给其赋空数组为默认值并将第二个参数回调函数添加进去。如下
else {(vm._events[event] || (vm._events[event] [])).push(fn)
}那么问题来了当前实例的_events属性是干嘛的呢
还记得我们在介绍生命周期初始化阶段的初始化事件initEvents函数中在该函数中首先在当前实例上绑定了_events属性并给其赋值为空对象如下
export function initEvents (vm: Component) {vm._events Object.create(null)// ...}这个_events属性就是用来作为当前实例的事件中心所有绑定在这个实例上的事件都会存储在事件中心_events属性中。
以上就是$on方法的内部原理。
2. vm.$emit
2.1 用法回顾
在介绍方法的内部原理之前我们先根据官方文档示例回顾一下它的用法。
vm.$emit( eventName, […args] )参数 {string} eventName[...args]作用 触发当前实例上的事件。附加参数都会传给监听器回调。
2.2 内部原理
该方法接收的第一个参数是要触发的事件名之后的附加参数都会传给被触发事件的回调函数。该方法的定义位于源码的src/core/instance/event.js中如下
Vue.prototype.$emit function (event: string): Component {const vm: Component thislet cbs vm._events[event]if (cbs) {cbs cbs.length 1 ? toArray(cbs) : cbsconst 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}
}该方法的逻辑很简单就是根据传入的事件名从当前实例的_events属性即事件中心中获取到该事件名所对应的回调函数cbs如下
let cbs vm._events[event]然后再获取传入的附加参数args如下
const args toArray(arguments, 1)由于cbs是一个数组所以遍历该数组拿到每一个回调函数执行回调函数并将附加参数args传给该回调。如下
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})}
}以上就是$emit方法的内部原理。
3. vm.$off
3.1 用法回顾
在介绍方法的内部原理之前我们先根据官方文档示例回顾一下它的用法。
vm.$off( [event, callback] )参数 {string | Arraystring} event (只在 2.2.2 支持数组){Function} [callback] 作用 移除自定义事件监听器。 如果没有提供参数则移除所有的事件监听器如果只提供了事件则移除该事件所有的监听器如果同时提供了事件与回调则只移除这个回调的监听器。
3.2 内部原理
通过用法回顾我们知道该方法用来移除事件中心里面某个事件的回调函数根据所传入参数的不同作出不同的处理。该方法的定义位于源码的src/core/instance/event.js中如下
Vue.prototype.$off function (event, fn) {const vm: Component this// allif (!arguments.length) {vm._events Object.create(null)return vm}// array of eventsif (Array.isArray(event)) {for (let i 0, l event.length; i l; i) {this.$off(event[i], fn)}return vm}// specific eventconst cbs vm._events[event]if (!cbs) {return vm}if (!fn) {vm._events[event] nullreturn vm}if (fn) {// specific handlerlet cblet i cbs.lengthwhile (i--) {cb cbs[i]if (cb fn || cb.fn fn) {cbs.splice(i, 1)break}}}return vm
}可以看到在该方法内部就是通过不断判断所传参数的情况进而进行不同的逻辑处理接下来我们逐行分析。
首先判断如果没有传入任何参数即arguments.length为0这就是第一种情况如果没有提供参数则移除所有的事件监听器。我们知道当前实例上的所有事件都存储在事件中心_events属性中要想移除所有的事件那么只需把_events属性重新置为空对象即可。如下
if (!arguments.length) {vm._events Object.create(null)return vm
}接着判断如果传入的需要移除的事件名是一个数组就表示需要一次性移除多个事件那么我们只需同订阅多个事件一样遍历该数组然后将数组中的每一个事件都递归调用$off方法进行移除即可。如下
if (Array.isArray(event)) {for (let i 0, l event.length; i l; i) {this.$off(event[i], fn)}return vm
}接着获取到需要移除的事件名在事件中心中对应的回调函数cbs。如下
const cbs vm._events[event]接着判断如果cbs不存在那表明在事件中心从来没有订阅过该事件那就谈不上移除该事件直接返回退出程序即可。如下
if (!cbs) {return vm
}接着如果cbs存在但是没有传入回调函数fn这就是第二种情况如果只提供了事件则移除该事件所有的监听器。这个也不难我们知道在事件中心里面一个事件名对应的回调函数是一个数组要想移除所有的回调函数我们只需把它对应的数组设置为null即可。如下
if (!fn) {vm._events[event] nullreturn vm
}接着如果既传入了事件名又传入了回调函数cbs也存在那这就是第三种情况如果同时提供了事件与回调则只移除这个回调的监听器。那么我们只需遍历所有回调函数数组cbs如果cbs中某一项与fn相同或者某一项的fn属性与fn相同那么就将其从数组中删除即可。如下
if (fn) {// specific handlerlet cblet i cbs.lengthwhile (i--) {cb cbs[i]if (cb fn || cb.fn fn) {cbs.splice(i, 1)break}}
}以上就是$off方法的内部原理。
4. vm.$once
4.1 用法回顾
在介绍方法的内部原理之前我们先根据官方文档示例回顾一下它的用法。
vm.$once( event, callback )参数 {string} event{Function} callback 作用 监听一个自定义事件但是只触发一次。一旦触发之后监听器就会被移除。
4.2 内部原理
该方法的作用是先订阅事件但是该事件只能触发一次也就是说当该事件被触发后会立即移除。要实现这个功能也不难我们可以定义一个子函数用这个子函数来替换原本订阅事件所对应的回调也就是说当触发订阅事件时其实执行的是这个子函数然后再子函数内部先把该订阅移除再执行原本的回调以此来达到只触发一次的目的。
下面我们就来看下源码的实现。该方法的定义位于源码的src/core/instance/event.js中如下
Vue.prototype.$once function (event, fn) {const vm: Component thisfunction on () {vm.$off(event, on)fn.apply(vm, arguments)}on.fn fnvm.$on(event, on)return vm
}可以看到在上述代码中被监听的事件是event其原本对应的回调是fn然后定义了一个子函数on。
在该函数内部先通过$on方法订阅事件同时所使用的回调函数并不是原本的fn而是子函数on如下
vm.$on(event, on)也就是说当事件event被触发时会执行子函数on。
然后在子函数内部先通过$off方法移除订阅的事件这样确保该事件不会被再次触发接着执行原本的回调fn如下
function on () {vm.$off(event, on)fn.apply(vm, arguments)
}另外还有一行代码on.fn fn是干什么的呢
上文我们说了我们用子函数on替换了原本的订阅事件所对应的回调fn那么在事件中心_events属性中存储的该事件名就会变成如下这个样子
vm._events {xxx:[on]
}但是用户自己却不知道传入的fn被替换了当用户在触发该事件之前想调用$off方法移除该事件时
vm.$off(xxx,fn)此时就会出现问题因为在_events属性中的事件名xxx对应的回调函数列表中没有fn那么就会移除失败。这就让用户费解了用户明明给xxx事件传入的回调函数是fn现在反而找不到fn导致事件移除不了了。
所以为了解决这一问题我们需要给on上绑定一个fn属性属性值为用户传入的回调fn这样在使用$off移除事件的时候$off内部会判断如果回调函数列表中某一项的fn属性与fn相同时就可以成功移除事件了。
以上就是$once方法的内部原理。