前端面試題:如何實(shí)現(xiàn)事件總線 Event Bus
介紹
通常作為多個(gè)模塊間的通信機(jī)制,相當(dāng)于一個(gè)事件管理中心,一個(gè)模塊發(fā)送消息,其它模塊接受消息,就達(dá)到了通信的作用。
原理
本質(zhì)上是采用了發(fā)布-訂閱的設(shè)計(jì)模式,比如多個(gè)模塊 A、B、C 訂閱了一個(gè)事件 EventX,然后某一個(gè)模塊 X 在事件總線發(fā)布了這個(gè)事件,那么事件總線會(huì)負(fù)責(zé)通知所有訂閱者 A、B、C,它們都能收到這個(gè)通知消息,同時(shí)還可以傳遞參數(shù)。
分析
如何使用 JavaScript 來實(shí)現(xiàn)一個(gè)簡單版本的 Event Bus
- 創(chuàng)建一個(gè)類
- 又一個(gè)事件池,用來保存發(fā)布的事件
- 有一個(gè)發(fā)布的方法,將事件發(fā)布
- 有一個(gè)訂閱的監(jiān)聽機(jī)制,來觸發(fā)事件的回調(diào)
- 有取消訂閱的機(jī)制
- 有只訂閱一次的機(jī)制
實(shí)現(xiàn)
創(chuàng)建一個(gè)類
class EventBus {}
創(chuàng)建一個(gè)事件池
class EventBus {
private events: {
[key: string]: Array<{fn: Function, isOnce: boolean}>
}
constructor() {
this.events = {}
}
}
發(fā)布事件
- 第一個(gè)參數(shù)是事件的KEY值, 剩余是接收事件的參數(shù)
- 在處理事件池中的監(jiān)聽時(shí),將只監(jiān)聽一次的事件去除
class EventBus {
private events: {
[key: string]: Array<{fn: Function, isOnce: boolean}>
}
constructor() {
this.events = {}
}
emit(type: string, ...args: any[]) {
const fnList = this.events[type]
if (fnList == null) return
this.events[type] = fnList.filter(item => {
const {fn, isOnce} = item
fn(...args)
return !isOnce
})
}
}
訂閱事件
class EventBus {
private events: {
[key: string]: Array<{fn: Function, isOnce: boolean}>
}
constructor() {
this.events = {}
}
on(type: string, fn: Function, isOnce:boolean=false){
const events = this.events
if (events[type] == null) {
this.events[type] = []
}
this.events[type].push({fn: fn, isOnce})
}
emit(type: string, ...args: any[]) {
const fnList = this.events[type]
if (fnList == null) return
this.events[type] = fnList.filter(item => {
const {fn, isOnce} = item
fn(...args)
return !isOnce
})
}
}
只訂閱一次
class EventBus {
private events: {
[key: string]: Array<{fn: Function, isOnce: boolean}>
}
constructor() {
this.events = {}
}
on(type: string, fn: Function, isOnce:boolean=false){
const events = this.events
if (events[type] == null) {
this.events[type] = []
}
this.events[type].push({fn: fn, isOnce})
}
once(type: string, fn: Function) {
this.on(type, fn, true)
}
emit(type: string, ...args: any[]) {
const fnList = this.events[type]
if (fnList == null) return
this.events[type] = fnList.filter(item => {
const {fn, isOnce} = item
fn(...args)
return !isOnce
})
}
}
取消訂閱
class EventBus {
private events: {
[key: string]: Array<{fn: Function, isOnce: boolean}>
}
constructor() {
this.events = {}
}
on(type: string, fn: Function, isOnce:boolean=false){
const events = this.events
if (events[type] == null) {
this.events[type] = []
}
this.events[type].push({fn: fn, isOnce})
}
once(type: string, fn: Function) {
this.on(type, fn, true)
}
emit(type: string, ...args: any[]) {
const fnList = this.events[type]
if (fnList == null) return
this.events[type] = fnList.filter(item => {
const {fn, isOnce} = item
fn(...args)
return !isOnce
})
}
off(type: string, fn: Function) {
this.events[type] = this.events[type].filter(item => {
return item.fn !== fn
})
}
}
取消訂閱某個(gè)事件
class EventBus {
private events: {
[key: string]: Array<{fn: Function, isOnce: boolean}>
}
constructor() {
this.events = {}
}
on(type: string, fn: Function, isOnce:boolean=false){
const events = this.events
if (events[type] == null) {
this.events[type] = []
}
this.events[type].push({fn: fn, isOnce})
}
once(type: string, fn: Function) {
this.on(type, fn, true)
}
emit(type: string, ...args: any[]) {
const fnList = this.events[type]
if (fnList == null) return
this.events[type] = fnList.filter(item => {
const {fn, isOnce} = item
fn(...args)
return !isOnce
})
}
off(type: string, fn: Function) {
// 如果沒傳 函數(shù)就是解除所有
if (!fn) {
this.events[type] = []
}else {
this.events[type] = this.events[type].filter(item => {
return item.fn !== fn
})
}
}
}
單例模式應(yīng)用
在上層實(shí)例中單例
將事件總線引入到上層實(shí)例使用,只需要保證在一個(gè)上層實(shí)例中只有一個(gè) EventBus,如果上層實(shí)例有多個(gè),意味著有多個(gè)事件總線,但是每個(gè)上層實(shí)例管控自己的事件總線。
首先在上層實(shí)例中建立一個(gè)變量用來存儲事件總線,只在第一次使用時(shí)初始化,后續(xù)其他模塊使用事件總線時(shí)直接取得這個(gè)事件總線實(shí)例。
// 上層實(shí)例
class LWebApp {
private _eventBus?: EventBus;
constructor() {}
public getEventBus() {
// 第一次初始化
if (this._eventBus == undefined) {
this._eventBus = new EventBus();
}
// 后續(xù)每次直接取唯一一個(gè)實(shí)例,保持在LWebApp實(shí)例中單例
return this._eventBus;
}
}
// 使用
const eventBus = new LWebApp().getEventBus();
在全局中單例
有時(shí)候我們希望不管哪一個(gè)模塊想使用我們的事件總線,我們都想這些模塊使用的是同一個(gè)實(shí)例,這就是全局單例,這種設(shè)計(jì)能更容易統(tǒng)一管理事件。
寫法同上面的類似,區(qū)別是要把 _eventBus 和 getEventBus 轉(zhuǎn)為靜態(tài)屬性。使用時(shí)無需實(shí)例化 EventBusTool 工具類,直接使用靜態(tài)方法就行了。
// 上層實(shí)例
class EventBusTool {
private static _eventBus?: EventBus;
constructor() {}
public static getEventBus(): EventBus {
// 第一次初始化
if (this._eventBus == undefined) {
this._eventBus = new EventBus();
}
// 后續(xù)每次直接取唯一一個(gè)實(shí)例,保持全局單例
return this._eventBus;
}
}
// 使用
const eventBus = EventBusTool.getEventBus();
2周刷完100道前端優(yōu)質(zhì)面試真題,雙越老師力作
鏈接:https://pan.baidu.com/s/1Fr4TUipNKGyaZxssKIkc3w
提取碼:ylsp
也歡迎使用我的小程序,驚喜不斷!

浙公網(wǎng)安備 33010602011771號