面試官:說說 Node 中的 EventEmitter? 如何實現一個 EventEmitter?
一、是什麼
我們瞭解到,Node
採用了事件驅動機制,而EventEmitter
就是Node
實現事件驅動的基礎
在EventEmitter
的基礎上,Node
幾乎所有的模塊都繼承了這個類,這些模塊擁有了自己的事件,可以綁定/觸發監聽器,實現了異步操作
Node.js
裏面的許多對象都會分發事件,比如 fs.readStream 對象會在文件被打開的時候觸發一個事件
這些產生事件的對象都是 events.EventEmitter 的實例,這些對象有一個 eventEmitter.on() 函數,用於將一個或多個函數綁定到命名事件上
二、使用方法
Node
的events
模塊只提供了一個EventEmitter
類,這個類實現了Node
異步事件驅動架構的基本模式——觀察者模式
在這種模式中,被觀察者 (主體) 維護着一組其他對象派來 (註冊) 的觀察者,有新的對象對主體感興趣就註冊觀察者,不感興趣就取消訂閱,主體有更新的話就依次通知觀察者們
基本代碼如下所示:
const EventEmitter = require('events')
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter()
function callback() {
console.log('觸發了event事件!')
}
myEmitter.on('event', callback)
myEmitter.emit('event')
myEmitter.removeListener('event', callback);
通過實例對象的on
方法註冊一個名爲event
的事件,通過emit
方法觸發該事件,而removeListener
用於取消事件的監聽
關於其常見的方法如下:
-
emitter.addListener/on(eventName, listener) :添加類型爲 eventName 的監聽事件到事件數組尾部
-
emitter.prependListener(eventName, listener):添加類型爲 eventName 的監聽事件到事件數組頭部
-
emitter.emit(eventName[, ...args]):觸發類型爲 eventName 的監聽事件
-
emitter.removeListener/off(eventName, listener):移除類型爲 eventName 的監聽事件
-
emitter.once(eventName, listener):添加類型爲 eventName 的監聽事件,以後只能執行一次並刪除
-
emitter.removeAllListeners([eventName]):移除全部類型爲 eventName 的監聽事件
三、實現過程
通過上面的方法瞭解,EventEmitter
是一個構造函數,內部存在一個包含所有事件的對象
class EventEmitter {
constructor() {
this.events = {};
}
}
其中events
存放的監聽事件的函數的結構如下:
{
"event1": [f1,f2,f3],
"event2": [f4,f5],
...
}
然後開始一步步實現實例方法,首先是emit
,第一個參數爲事件的類型,第二個參數開始爲觸發事件函數的參數,實現如下:
emit(type, ...args) {
this.events[type].forEach((item) => {
Reflect.apply(item, this, args);
});
}
當實現了emit
方法之後,然後實現on
、addListener
、prependListener
這三個實例方法,都是添加事件監聽觸發函數,實現也是大同小異
on(type, handler) {
if (!this.events[type]) {
this.events[type] = [];
}
this.events[type].push(handler);
}
addListener(type,handler){
this.on(type,handler)
}
prependListener(type, handler) {
if (!this.events[type]) {
this.events[type] = [];
}
this.events[type].unshift(handler);
}
緊接着就是實現事件監聽的方法removeListener/on
removeListener(type, handler) {
if (!this.events[type]) {
return;
}
this.events[type] = this.events[type].filter(item => item !== handler);
}
off(type,handler){
this.removeListener(type,handler)
}
最後再來實現once
方法, 再傳入事件監聽處理函數的時候進行封裝,利用閉包的特性維護當前狀態,通過fired
屬性值判斷事件函數是否執行過
once(type, handler) {
this.on(type, this._onceWrap(type, handler, this));
}
_onceWrap(type, handler, target) {
const state = { fired: false, handler, type , target};
const wrapFn = this._onceWrapper.bind(state);
state.wrapFn = wrapFn;
return wrapFn;
}
_onceWrapper(...args) {
if (!this.fired) {
this.fired = true;
Reflect.apply(this.handler, this.target, args);
this.target.off(this.type, this.wrapFn);
}
}
完整代碼如下:
class EventEmitter {
constructor() {
this.events = {};
}
on(type, handler) {
if (!this.events[type]) {
this.events[type] = [];
}
this.events[type].push(handler);
}
addListener(type,handler){
this.on(type,handler)
}
prependListener(type, handler) {
if (!this.events[type]) {
this.events[type] = [];
}
this.events[type].unshift(handler);
}
removeListener(type, handler) {
if (!this.events[type]) {
return;
}
this.events[type] = this.events[type].filter(item => item !== handler);
}
off(type,handler){
this.removeListener(type,handler)
}
emit(type, ...args) {
this.events[type].forEach((item) => {
Reflect.apply(item, this, args);
});
}
once(type, handler) {
this.on(type, this._onceWrap(type, handler, this));
}
_onceWrap(type, handler, target) {
const state = { fired: false, handler, type , target};
const wrapFn = this._onceWrapper.bind(state);
state.wrapFn = wrapFn;
return wrapFn;
}
_onceWrapper(...args) {
if (!this.fired) {
this.fired = true;
Reflect.apply(this.handler, this.target, args);
this.target.off(this.type, this.wrapFn);
}
}
}
測試代碼如下:
const ee = new EventEmitter();
// 註冊所有事件
ee.once('wakeUp', (name) => { console.log(`${name} 1`); });
ee.on('eat', (name) => { console.log(`${name} 2`) });
ee.on('eat', (name) => { console.log(`${name} 3`) });
const meetingFn = (name) => { console.log(`${name} 4`) };
ee.on('work', meetingFn);
ee.on('work', (name) => { console.log(`${name} 5`) });
ee.emit('wakeUp', 'xx');
ee.emit('wakeUp', 'xx'); // 第二次沒有觸發
ee.emit('eat', 'xx');
ee.emit('work', 'xx');
ee.off('work', meetingFn); // 移除事件
ee.emit('work', 'xx'); // 再次工作
參考文獻
-
http://nodejs.cn/api/events.html#events_class_eventemitter
-
https://segmentfault.com/a/1190000015762318
-
https://juejin.cn/post/6844903781230968845
-
https://vue3js.cn/interview
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/xiUQ2CfraZB_2ZojLS5uFw