EventEmitter,普遍的應用於javascript語言中,瀏覽器事件(如鼠標單擊click,鍵盤事件keyDown)都是該模式的例子。咱們在編寫代碼時也常常用它來解耦,好比:組件間咱們不想經過大量狀態來通訊時,能夠考慮用它來編寫代碼。不只如此,面試時可能面試官會讓咱們實現一個EventEmitter,這時候可能有不少人就不會了。javascript
網上已經有不少EventEmitter的實現了,那我爲啥還要寫一個;首先,看一百次不如本身動手寫一次;其次,這個模塊很經常使用,本身定製的話也容易根據實際業務狀況的變更作修改。java
以Nodejs 的 EventEmitter API爲參考,咱們大概要實現如下的API:面試
首先咱們須要存儲全部監聽事件的地方,那麼如何存儲呢?常見的方法是使用哈希表,由於時間複雜度是 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);
}
複製代碼
上面有兩點須要注意下,首先,每一個事件的初始值是一個數組,由於對於一個事件,咱們可能會作多件事; 其次,監聽事件一次,只是在調用監聽事件方法時多加了一個參數,至於爲何,咱們看看後面的觸發事件方法就能明白。markdown
// 觸發事件
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);
}
}
複製代碼
這裏就對兩種不一樣狀況下的事件監聽作了不一樣處理。app
// 取消事件監聽
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;
}
複製代碼
這裏的取消事件監聽分了幾種不一樣狀況,根據參數去判斷。flex
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;
}
}
複製代碼
看完個人,但願你們也能動手實現一個,本身寫的才更不容易忘記。this