前言
EventEmitter,广泛的应用于javascript语言中,浏览器事件(如鼠标单击click,键盘事件keyDown)都是该模式的例子。我们在编写代码时也经常用它来解耦,比如:组件间我们不想通过大量状态来通信时,可以考虑用它来编写代码。不仅如此,面试时可能面试官会让我们实现一个EventEmitter,这时候可能有很多人就不会了。
网上已经有很多EventEmitter的实现了,那我为啥还要写一个;首先,看一百次不如自己动手写一次;其次,这个模块很常用,自己定制的话也容易根据实际业务情况的变动做修改。
代码实现
以Nodejs 的 EventEmitter API为参考,我们大概要实现以下的API:
- on() // 监听事件
- once() // 监听事件一次
- emit() // 触发事件
- off() // 取消监听
- getEvents() // 获取所有的监听事件
首先我们需要存储所有监听事件的地方,那么如何存储呢?常见的方法是使用哈希表,因为时间复杂度是 O(1),空间复杂度一般也不会太大。所以我们的存储方式是:
interface EventType {
readonly callback: Function;
readonly once: boolean;
}
interface EventMap {
[propName: string]: EventType[]
}
export default class EventEmitter {
private eventMap: EventMap = {};
}
复制代码
接下来就是实现具体的方法
监听事件
// 监听事件
on(event: string, callback: Function, once?: boolean) {
if (!this.eventMap[event]) {
this.eventMap[event] = [];
}
this.eventMap[event].push({
callback,
once: !!once,
});
return this;
}
// 监听事件一次
once(event: string, callback: Function) {
return this.on(event, callback, true);
}
复制代码
上面有两点需要注意下,首先,每个事件的初始值是一个数组,因为对于一个事件,我们可能会做多件事;
其次,监听事件一次,只是在调用监听事件方法时多加了一个参数,至于为什么,我们看看后面的触发事件方法就能明白。
触发事件
// 触发事件
emit(event: string, ...args: any[]) {
const events = this.eventMap[event] || [];
let length = events.length;
for (let i = 0; i < length; i++) {
if (!events[i]) {
continue;
}
const { callback, once } = events[i];
if (once) {
events.splice(i, 1);
if (events.length === 0) {
delete this.eventMap[event];
}
length--;
i--;
}
callback.apply(this, args);
}
}
复制代码
这里就对两种不同情况下的事件监听做了不同处理。
取消事件监听
// 取消事件监听
off(event?: string, callback?: Function) {
if(!event) {
// event 为空全部清除
this.eventMap = {}
} else {
if(!callback) {
// event 存在,但callback不存在
delete this.eventMap[event]
} else {
// event 存在,callback 存在,清除匹配的方法
const events = this.eventMap[event] || [];
let length = events.length;
for (let i = 0; i < length; i++) {
if (events[i].callback === callback) {
events.splice(i, 1);
length--;
i--;
}
}
if (events.length === 0) {
delete this.eventMap[event];
}
}
}
return this;
}
复制代码
这里的取消事件监听分了几种不同情况,根据参数去判断。
完整代码
interface EventType {
readonly callback: Function;
readonly once: boolean;
}
interface EventMap {
[propName: string]: EventType[]
}
export default class EventEmitter {
private eventMap: EventMap = {};
// 监听事件
on(event: string, callback: Function, once?: boolean) {
if (!this.eventMap[event]) {
this.eventMap[event] = [];
}
this.eventMap[event].push({
callback,
once: !!once,
});
return this;
}
// 监听事件一次
once(event: string, callback: Function) {
return this.on(event, callback, true);
}
// 触发事件
emit(event: string, ...args: any[]) {
const events = this.eventMap[event] || [];
let length = events.length;
for (let i = 0; i < length; i++) {
if (!events[i]) {
continue;
}
const { callback, once } = events[i];
if (once) {
events.splice(i, 1);
if (events.length === 0) {
delete this.eventMap[event];
}
length--;
i--;
}
callback.apply(this, args);
}
}
// 取消事件监听
off(event?: string, callback?: Function) {
if(!event) {
// event 为空全部清除
this.eventMap = {}
} else {
if(!callback) {
// event 存在,但callback不存在
delete this.eventMap[event]
} else {
// event 存在,callback 存在,清除匹配的方法
const events = this.eventMap[event] || [];
let length = events.length;
for (let i = 0; i < length; i++) {
if (events[i].callback === callback) {
events.splice(i, 1);
length--;
i--;
}
}
if (events.length === 0) {
delete this.eventMap[event];
}
}
}
return this;
}
// 获取当前所有的事件
getEvents() {
return this.eventMap;
}
}
复制代码
看完我的,希望大家也能动手实现一个,自己写的才更不容易忘记。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END