提到mittjs,一定或多或少都在项目中使用过它,无论你是使用Vue,抑或是React,你想实现跨组件通信他都是一个非常好的选择。
它有什么特点呢?
- 它是基于发布订阅模式,并且是单向数据流。
- 它可以监听多个事件,并且可以对事件进行过滤。
- 它可以绑定和解绑事件。
我们要如何实现呢? 当然是先明白它的基本原理啦!
发布订阅流程大概如下:
已经明白它的基本原理,那么我们实现它已更近一步了!
- 首先,我们需要一个储存事件的对象,我们可以使用一个Map来实现,这个Map的键是事件类型,值是事件处理函数的数组。
- 我们可以用on方法来绑定事件(订阅),这个方法接收两个参数,第一个参数是事件类型,第二个参数是事件处理函数。
- 我们可以用emit方法来触发事件(发布),这个方法接收两个参数,第一个参数是事件类型,第二个参数传递给订阅者的data。
- 我们可以用off方法来解绑事件,这个方法接收两个参数,第一个参数是事件类型,第二个参数是事件处理函数。
class Mitt {
handlers = new Map();
//监听
/**
*
* @param { String } event 订阅事件名称
* @param { Function} handler 订阅事件处理函数
*/
on(event, handler) {}
/**
*
* @param { String } event 订阅事件名称
* @param { Array } args 传递的参数
*/
emit(event, ...args) {}
/**
*
* @param { String } event 取消订阅事件名称
* @param { Function} handler 取消订阅事件处理函数
*/
off(event, handler) {}
}
我们已经完成它的基本类结构,接下来让我们使用一点小魔法,让它运转起来!
class Mitt {
handlers = new Map();
/**
*
* @param { String } event 订阅事件名称
* @param { Function} handler 订阅事件处理函数
*/
on(event, handler) {
if (this.handlers.get(event)) {
this.handlers.set(event, [...this.handlers.get(event), handler]);
} else {
this.handlers.set(event, [handler]);
}
}
/**
*
* @param { String } event 订阅事件名称
* @param { Array } args 传递的参数
*/
emit(event, ...args) {
// 处理监听事件
const bindHandler = this.handlers.get(event);
if (bindHandler) {
bindHandler.forEach((handler) => {
handler(...args);
});
}
}
/**
*
* @param { String } event 取消订阅事件名称
* @param { Function} handler 取消订阅事件处理函数
*/
off(event, handler) {
this.handlers.clear();
}
}
我们已经完成了基本的类结构,接下来我们来实现一个简单的例子,来验证一下我们的类是否正确。
const mitt = new Mitt();
// 绑定事件
mitt.on('event1', (a, b) => {
console.log(a + b);
});
mitt.on('event1', (a, b) => {
console.log(a, b);
});
mitt.on('event2', (c) => {
console.log(c);
});
// 触发事件
mitt.emit('event1', 1, 2);
mitt.emit('event2', 3);
console.log(mitt.handlers);
输出结果如下:
至此我们已经完成了mittjs的基本实现,你可以在单文件中实现事件发布与订阅,并且可以绑定和解绑事件。
该实例还有很多优化!
-
我们每次重新
new Mitt实例化一个新的对象,会导致handlers被清空,所以我们需要在mitt对象被实例化后,将handlers保存起来,以便后续使用(可以把handlers提到class外面保存,亦或者添加单例模式,确保只有一个实例对象) ; -
handlersnew Mitt实例可以直接访问和修改也会造成不可预知的问题(采用私有字段解决)
以下为作者的完整实施方案
class Mitt {
// 定义为私有字段 防止通过实例进行更改
#handlers = new Map();
static instance;
constructor() {
if (!Mitt.instance) {
Mitt.instance = this;
}
return Mitt.instance;
}
//通过handlers获取收集的所有依赖
get handlers() {
// 将map结构转为普通对象
return Object.fromEntries(this.#handlers);
}
/**
*
* @param { String } event 订阅事件名称
* @param { Function} handler 订阅事件处理函数
*/
on(event, handler) {
if (this.#handlers.get(event)) {
this.#handlers.set(event, [...this.#handlers.get(event), handler]);
} else {
this.#handlers.set(event, [handler]);
}
}
/**
*
* @param { String } event 订阅事件名称
* @param { Array } args 传递的参数
*/
// 触发时监听需要先注册,不然就无法触发
emit(event, ...args) {
// 处理监听事件
const bindHandler = this.#handlers.get(event);
if (bindHandler) {
bindHandler.forEach((handler) => {
handler(...args);
});
}
// 处理 all监听事件
const allHandler = this.#handlers.get('*');
if (allHandler) {
allHandler.forEach((handler) => {
handler(event, ...args);
});
}
}
/**
*
* @param { String } event 取消订阅事件名称
* @param { Function} handler 取消订阅事件处理函数
*/
off(event, handler) {
this.#handlers.delete(event);
handler();
}
// 清除所有监听
clear() {
this.#handlers.clear();
}
}