什么是发布/订阅模式?
一张图来解释:
eventbus.png
发布者发布一项内容给控制中心 控制中心将内容发送给对应的订阅者
当我们需要在完全没有联系的组件之间通信,经常会用到,下面开始code。
开始code一个最简单的
首先我们先创建一个类 EventBus
class EventBus {
}
类创建好了 我们需要思考一下我们这个类需要哪些方法和属性。
首先我们肯定需要一个发布方法 一个订阅方法。其次我们还需要一个存储中心来存储事件以及对应的订阅函数,下面使我们目前想到的方法和属性。
- 发布方法 fire
- 订阅方法 on
- 存储中心 events
ok 我们的eventBus类现在长成了这样
class EventBus {
constructor() {
this.events = {}
}
on = (eventName, callback) => {
this.events[eventName] = callback; // 以事件名为key 订阅函数为value存储在events里
}
fire = (eventName) => {
this.events[eventName]() // 调用事件对应的订阅函数
}
}
现在完成了一个最简单的eventBus, 我们来测试一下 看看效果
const eventBust = new EventBus()
eventBus.on('greet'); // 订阅事件 greet
setTimeout(() => {
eventBus.fire('greet') // 3s 后触发 greet 事件
}, 3000);
eventBus_1.gif
给事件的订阅函数传递参数
ok 这个简单的eventBus可以正常运行了,下面我们要是想在触发事件的同时传一些参数进去呢?
我们将fire改造一下就可以啦 用上扩展运算法 ‘...’ 想传几个参数就传几个参数。
fire(eventName, ...arg) {
this.events[eventName].call(this, ...arg) // 使用... 扩展运算符将所有参数传入订阅函数
}
测试一下
const eventBus = new EventBus()
eventBus.on('greet', (name1, name2) => {
console.log(`hello ${name1} and ${name2} !`);
}); // 订阅事件 greet
setTimeout(() => {
eventBus.fire('greet', '小明', '小红') // 3s 后触发 greet 事件 并传入两个参数
}, 3000);
eventBus_2.gif
取消事件的所有订阅程序
如果我们想要取消对某个事件的订阅呢?
回顾一下 我们现在的eventBus具有的方法和属性,同时添加一个取消的方法。这是现在eventBus的样子
- 发布方法 fire
- 订阅方法 on
- 存储中心 events
- 取消订阅某一事件的所有方法 offAll
改造一下fire并添加一个offAll方法
fire(eventName, ...arg) {
if(typeof this.events[eventName] === 'function') {
this.events[eventName].call(this, ...arg);
}
}
offAll(eventName) {
Reflect.deleteProperty(this.events, eventName); // 删除对某一事件的订阅
}
测试一下
const eventBus = new EventBus()
eventBus.on('greet', (name1, name2) => {
console.log(`hello ${name1} and ${name2} !`);
}); // 订阅事件 greet
eventBus.offAll('greet'); // 取消订阅
setTimeout(() => {
eventBus.fire('greet', '小明', '小红') // 3s 后触发 greet 事件
console.log('若上面没有log出内容 说明取消订阅成功');
}, 3000);
eventBus_3.gif
取消事件特定的订阅程序
上面的offAll 是取消了对某一事件订阅所有订阅函数 加入我们只想取消某一个订阅程序呢?
加一个off方法, 同事改造一下on和fire方法
on(eventName, callback) {
// 可能同一个事件注册了不同的订阅函数
if(this.events[eventName]) {
this.events[eventName].push(callback);
}else {
this.events[eventName] = [callback];
}
}
fire(eventName, ...arg) {
if(Reflect.has(this.events, eventName)) {
this.events[eventName].forEach(fn => {
fn.call(this, ...arg);
});
}
}
off(eventName, fnName) { // 根据订阅程序的函数名称移除
const fns = this.events[eventName];
const targetIndex = fns.findIndex(fn => (fn.name === fnName));
fns.splice(targetIndex, 1); // 删除指定订阅函数
}
测试一下
const eventBus = new EventBus()
const fn1 = () => {
console.log('hello fn1 !!');
}
const fn2 = () => {
console.log('hello fn2 !!');
}
eventBus.on('greet',fn1); // 订阅事件 greet
eventBus.on('greet',fn2); // 订阅事件 greet
eventBus.off('greet', 'fn1'); // 取消对greet事件的订阅函数 fn1
setTimeout(() => {
eventBus.fire('greet') // 3s 后触发 greet 事件
}, 3000);
eventBus_4.gif
从上图可看到 只执行了fn2 没有执行fn1 说明取消订阅fn1的函数 成功!
添加类型校验
这个eventBus基本完成了我们还要在优化一下 添加一些类型校验,使代码更加健壮
最终完成的代码如下
/**
* events: {}
* events.key @type string 事件名称
* events.value @type array 事件注册时的回调函数组成的数组
*/
class EventBus {
constructor() {
this.events = {}
}
/**
* 注册对事件的订阅
* @param {*} eventName 事件名称
* @param {*} callback 订阅程序
* @memberof EventBus
*/
on(eventName, callback) {
if(typeof eventName !== 'string') {
throw new Error('eventName expected string');
}
if(typeof callback !=='function') {
throw new Error('callback expected function');
}
// 可能同一个事件注册了不同的回调函数
if(this.events[eventName]) {
this.events[eventName].push(callback);
}else {
this.events[eventName] = [callback]; // 此事件还未注册回调
}
}
/**
*触发事件
*
* @param {*} eventName 事件名称
* @param {*} arg 传给订阅程序的参数
* @memberof EventBus
*/
fire(eventName, ...arg) {
if(typeof eventName !== 'string') {
throw new Error('eventName expected string');
}
if(Reflect.has(this.events, eventName)) {
this.events[eventName].forEach(fn => {
fn.call(this, ...arg);
});
}
}
/**
* 取消订阅指定函数的事件
*
* @param {*} eventName 事件名称
* @param {*} fnName 订阅程序的名称
* @memberof EventBus
*/
off(eventName, fnName) {
const fns = this.events[eventName];
const targetIndex = fns.findIndex(fn => (fn.name === fnName));
fns.splice(targetIndex, 1); // 删除指定回调
}
/**
*取消所有指定事件的订阅
*
* @param {*} eventName 事件名称
* @memberof EventBus
*/
offAll(eventName) {
Reflect.deleteProperty(this.events, eventName)
}
}
网友评论