众所周知,EventEmitter是咱们前端开发常常须要使用的一个类(node端自带,浏览器端gaythub上有许多现成的npm包)。它可以帮助咱们订阅、发送自定义事件,在跨组件通讯中常常用到。但使用这个类的时候有个问题:咱们必须在发射某事件前订阅它,后来订阅者是没法接收前面的事件的。前端
项目中每每一些千奇百怪的bug在定位后发现,就是由于错误这个时机而形成的:( 所以咱们须要额外当心这个订阅时机。node
为了解决这个说大不大,说小不小的问题,今天咱们就来造个轮子,实现一个带历史消息的EventEmiitter吧typescript
首先咱们先实现一个普通的EventEmitter,它应该包含on
、off
、emit
三个基本的API,此外,为了更好地说明一些参数格式,我这里使用了typescript,不了解ts的童鞋也不要紧,它的格式很是简单易懂~npm
class EventEmitter {
private eventStack: Array<{
name: string;
callback: Function;
}>;
constructor() {
this.eventStack = [];
}
// 订阅事件
on(name: string, callback: Function) {
this.eventStack.push({ name, callback });
}
// 取消订阅
off(name?: string, callback?: Function) {
if (!name) {
this.eventStack = [];
} else if (!callback) {
this.eventStack.filter(item => item.name !== name);
} else {
this.eventStack.filter(
item => item.name !== name && item.callback !== callback,
);
}
}
// 发射事件
emit(name: string, data?: any) {
data = data || null;
this.eventStack.forEach(item => {
item.name === name && item.callback(data);
});
}
}
复制代码
首先咱们定义了一个EventEmitter
类,同时也定义了on
、off
、emit
三个基本API,同时定义了一个内部属性eventStack
—— 一个存放订阅对象的数组(订阅对象是一个由事件名name
和回调函数callback
组成的对象),它是实现该类的核心。数组
当调用者执行on
方法时,其实就是将事件名和回调函数组成订阅者对象,而后推入eventStack
浏览器
因为off
支持取消订阅某一事件的某一回调、取消订阅某一事件的全部回调,以及取消全部事件的订阅三种状况,所以这里根据传参的有无,利用filter
删除了eventStack
的部分订阅对象函数
这个方法也很简单,利用forEach
找到一致事件名的订阅对象,执行其回调便可“发射出去”ui
接下来是重点内容,为了让后来订阅者能接收先前的消息,咱们须要增长一个叫“历史消息”的东西,它应该存放EventEmitter
从建立之初到迄今的全部事件和数据,所以咱们新增了一个内部属性:this
// 历史消息,name:事件名,data:消息数据
private historyData: Array<{
name: string;
data: any;
}>;
复制代码
而后咱们在emit
方法里作一些更改,在发射当前消息之余,咱们也应该将其保存到historyData
中:spa
emit(name: string, data?: any) {
data = data || null;
// 保存事件历史
this.historyData.push({ name, data });
// 发射事件
this.eventStack.forEach(item => {
item.name === name && item.callback(data);
});
}
复制代码
其次,在on
方法中咱们也须要进行相应修改:在订阅的一刹那查阅下历史消息,康康是否存在同类型的事件,若是有,则直接执行回调,此外,为了提升on
方法的扩展性,咱们增长了第三个参数,用于决定是否开启“历史消息推送”功能:
on(name: string, callback: Function, isNeedHistoryData?: boolean) {
this.eventStack.push({ name, callback });
isNeedHistoryData &&
this.historyData.forEach(item => {
item.name === name && callback(item.data);
});
}
复制代码
finally,咱们的最终代码长这样:
export default class Event {
private historyData: Array<{
name: string;
data: any;
}>;
private eventStack: Array<{
name: string;
callback: Function;
}>;
constructor() {
this.historyData = [];
this.eventStack = [];
}
on(name: string, callback: Function, isNeedHistoryData?: boolean) {
this.eventStack.push({ name, callback });
isNeedHistoryData &&
this.historyData.forEach(item => {
item.name === name && callback(item.data);
});
}
off(name?: string, callback?: Function) {
if (!name) {
this.eventStack = [];
} else if (!callback) {
this.eventStack.filter(item => item.name !== name);
} else {
this.eventStack.filter(
item => item.name !== name && item.callback !== callback,
);
}
}
emit(name: string, data?: any) {
data = data || null;
// 保存事件历史
this.historyData.push({ name, data });
// 发射事件
this.eventStack.forEach(item => {
item.name === name && item.callback(data);
});
}
}
复制代码
以上代码拷贝后就能够直接运用到项目中了,是否是很简单?
今后之后,妈妈不再用担忧我错过订阅事件的时机啦n(≧▽≦)n