【业务|手撕发布订阅模式 eventBus】什么是发布订阅模式
比如我们很喜欢看某个公众号号的文章,但是我们不知道什么时候发布新文章,要不定时的去翻阅;这时候,我们可以关注该公众号,当有文章推送时,会有消息及时通知我们文章更新了。
上面一个看似简单的操作,其实是一个典型的发布订阅模式,公众号属于发布者,用户属于订阅者;用户将订阅公众号的事件注册到调度中心,公众号作为发布者,当有新文章发布时,公众号发布该事件到调度中心,调度中心会及时发消息告知用户。
1-1. eventBus的基本使用 我们要实现eventBus,首先肯定要知道eventBus它目前具有哪些api
注册EventBus
//main.js
import Vue from 'vue';
...
Vue.prototype.EventBus = new Vue();
vm.$on( event, callback )
用法:监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数。
vm.$emit( eventName, […args] )
用法:触发当前实例上的事件。附加参数都会传给监听器回调。
vm.$once( event, callback )
用法:监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除。
vm.$off( [event, callback] )
用法:
- 移除自定义事件监听器。
- 如果没有提供参数,则移除所有的事件监听器;
- 如果只提供了事件,则移除该事件所有的监听器;
- 如果同时提供了事件与回调,则只移除这个回调的监听器。
import Son from '../components/Son';
export default {
components: {
Son,
},
mounted () {
//监听grandson事件
this.EventBus.$on("grandson",this.handleMmsg);
},
methods: {
handleMmsg(msg) {
....
//移除事件监听,监听以后马上又移除了,故Parent只监听一次
this.EventBus.$off("grandson",this.handleMmsg);
}
},
}
子组件
我是子组件
>
import Grandson from '../components/Grandson';
export default {
components: {
Grandson,
},
mounted () {
//监听事件
this.EventBus.$on("grandson",(msg)=>{
console.log(msg);
});
},
}
孙组件
我是孙子组件
>
export default {
methods: {
handleMsg() {
//发出grandson事件
this.EventBus.$emit("grandson","我是组件GrandSon")
}
},
}
ok,现在我们来实现吧!!
1-2.实现 实现思路
- 创建一个对象
- 在该对象上创建一个缓存列表(调度中心)
- on 方法用来把函数 fn 都加到缓存列表中(订阅者注册事件到调度中心)
- emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)
- off 方法可以根据 event 值取消订阅(取消订阅)
- once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)
class EventEmitter {
constructor() {
// 维护事件及监听者
this.listeners = {}
}/**
* 注册事件监听者
* @param {String} type 事件类型,例如上面写的grandson事件
* @param {Function} cb 回调函数,例如上面写的on接收的grandson对应的函数
* 例如: EventBus.$on("grandson",(msg)=>{console.log(msg);
})
*/
on(type, cb) {
if (!this.listeners[type]) {
this.listeners[type] = []
}
this.listeners[type].push(cb)
}/**
* 发布事件
* @param {String} type 事件类型
* @param{...any} args 参数列表,把emit传递的参数赋给回调函数
* 例如: EventBus.$emit("grandson","我是组件GrandSon")
*/
emit(type, ...args) {
if (this.listeners[type]) {// 只要emit一触发,那么所有监听这个事件的on都会接收到,然后执行
this.listeners[type].forEach(cb => {
cb(...args)
})
}
}/**
* 移除某个事件的一个监听者
* @param {String} type 事件类型
* @param {Function} cb 回调函数
* 例如:EventBus.$off("grandson",this.handleMmsg);
*/
off(type, cb) {
if (this.listeners[type]) {
const targetIndex = this.listeners[type].findIndex(item => item === cb)
if (targetIndex !== -1) {
this.listeners[type].splice(targetIndex, 1)
}
if (this.listeners[type].length === 0) {
delete this.listeners[type]
}
}
}/**
* 移除某个事件的所有监听者
* @param {String} type 事件类型
*/
offAll(type) {
if (this.listeners[type]) {
delete this.listeners[type]
}
}
}// 创建事件管理器实例
const ee = new EventEmitter()
// 注册一个chifan事件监听者
ee.on('chifan', function () { console.log('吃饭了,我们走!') })
// 发布事件chifan
ee.emit('chifan')
// 也可以emit传递参数
ee.on('chifan', function (address, food) { console.log(`吃饭了,我们去${address}吃${food}!`) })
ee.emit('chifan', '三食堂', '铁板饭') // 此时会打印两条信息,因为前面注册了两个chifan事件的监听者// 测试移除事件监听
const toBeRemovedListener = function () { console.log('我是一个可以被移除的监听者') }
ee.on('testoff', toBeRemovedListener)
ee.emit('testoff')
ee.off('testoff', toBeRemovedListener)
ee.emit('testoff') // 此时事件监听已经被移除,不会再有console.log打印出来了// 测试移除chifan的所有事件监听
ee.offAll('chifan')
console.log(ee) // 此时可以看到ee.listeners已经变成空对象了,再emit发送chifan事件也不会有反应了
特点
- 发布订阅模式中,对于发布者
Publisher
和订阅者Subscriber
没有特殊的约束,他们好似是匿名活动,借助事件调度中心提供的接口发布和订阅事件,互不了解对方是谁。
- 松散耦合,灵活度高,常用作事件总线
- 易理解,可类比于
DOM
事件中的dispatchEvent
和addEventListener
。
文章图片
缺点:
当事件类型越来越多时,难以维护,需要考虑事件命名的规范,也要防范数据流混乱。
推荐阅读
- vue的事件总线|vue2 eventbus的实现原理
- vue3发布订阅者模式|vue3的EventBus库(mitt)与mitt的使用方式
- java|使用maven创建web项目
- 2022秋招准备|牛客面试刷题
- Vue基础知识|Vue基础知识
- JavaScript|JavaScript基础(6)_流程控制语句
- DGIOT数字工厂整体结构介绍
- three.js|Three.js - 模拟太阳、地球、月亮的运动(十一)
- #|微信小程序开发之图片压缩方案