Events
描述
- 大多數 Node.js API 採用非同步事件驅動架構,這些物件都是
EventEmitter
類的例項(Emitter),通過觸發命名事件(eventName or type)來呼叫函式(監聽器,listener) - Emitter 觸發事件時,可以向監聽器函式傳遞任意數量的引數,所有註冊到該事件上的監聽器函式都會依次同步執行,函式的返回值會被忽略
事件
- 命名規範:駝峰式字串級任何有效的 JavaScript 屬性鍵
error
error
事件被視為特殊情況,如果沒有註冊監聽器會導致丟擲錯誤、列印堆疊跟蹤並退出 Node.js 程式,應始終為error事件註冊監聽器
errorMonitor
-
為
errorMonitor
事件註冊的監聽器不會消耗 error,如果沒有為 error 事件註冊監聽器,依然會導致丟擲錯誤、列印堆疊跟蹤並退出 Node.js 程式。'use strict' const EventEmitter = require('events').EventEmitter; const ee = new EventEmitter({ captureRejections: true }); ee.on(EventEmitter.errorMonitor, function () { console.log('ErrorMonitor, call first'); }) ee.on('error', () => { console.log('customer error listener'); }) ee.emit('error');
newListener
-
當有新的監聽器被新增時,所有
Emitter
都會觸發'newListener'
事件。 -
為該事件註冊監聽器相等於一個鉤子函式,可以獲取到
事件的名稱
和要新增的監聽器的引用
class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); // 只處理一次,避免無限迴圈。 myEmitter.once('newListener', (event, listener) => { console.log(`為${event}新增事件${listener}`) })
removeListener
- 當現有的監聽器被移除時,所有
Emitter
都會觸發'removeListener'
事件。 - 為該事件註冊監聽器相等於一個鉤子函式,可以獲取到
事件的名稱
和要新增的監聽器的引用
監聽器
this指向
普通函式中的this是觸發事件的Emitter
,箭頭函式中的this是{}
const EventEmitter = require('events').EventEmitter;
const ee = new EventEmitter();
function f1() {
console.log('run f1, this = ', this);
}
const f2 = () => {
console.log('run arrow function, this = ', this);
}
ee.on('test', f1);
ee.on('test', f2);
ee.emit('test');
非同步監聽器
如果新增非同步監聽器,需要開啟captureRejections
選項且實現captureRejectionSymbol
方法
const { EventEmitter, captureRejectionSymbol } = require('events');
const ee = new EventEmitter({ captureRejections: true });
// 3中方式獲取 kRejection 的 Symbol 值
ee[ee.constructor.captureRejectionSymbol] = function (err, event, ...args) {
console.log('rejection happened for', event, 'with', err, ...args);
}
ee[EventEmitter.captureRejectionSymbol] = function (err, event, ...args) {
console.log('rejection happened for', event, 'with', err, ...args);
}
ee[captureRejectionSymbol] = function (err, event, ...args) {
console.log('rejection happened for', event, 'with', err, ...args);
}
async function f() {
return Promise.reject('async rejection');
}
ee.on('test', f);
ee.emit('test');
文件中這句話沒太明白
The
'error'
events that are generated by thecaptureRejections
behavior do not have a catch handler to avoid infinite error loops: the recommendation is to not useasync
functions as'error'
event handlers.
執行次數
-
通過
EventEmitter#on()
方式註冊的監聽器,每次觸發命名事件都會執行 -
通過
EventEmitter#once()
方式註冊的監聽器,觸發命名事件只會執行一次觸發
once
事件時,先觸發removeListener
事件移除監聽器,再呼叫執行。在removeListener監聽器
中可拿到once 監聽器
,可多次執行const EventEmitter = require('events').EventEmitter; let ee = new EventEmitter() ee.once('test', () => console.log('test')) ee.on('removeListener', (eventName, listener) => { console.log(`eventName: ${eventName}`) listener() // 多次執行 listener() listener() }) ee.emit('test') // eventName: test // test // test // test // test
原始碼 v16.10
程式碼註釋 https://github.com/lfp1024/node/blob/master/lib/events.js
常量
const kRejection = SymbolFor('nodejs.rejection');
const kCapture = Symbol('kCapture');
const kErrorMonitor = Symbol('events.errorMonitor');
const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
const kMaxEventTargetListenersWarned =
Symbol('events.maxEventTargetListenersWarned');
字首 k 表示常量 (德語 konstant)
建構函式
function EventEmitter(opts) {
EventEmitter.init.call(this, opts); // 使 init 方法中的 this 指向新建立的例項,而非 EventEmitter 本身
}
module.exports = EventEmitter;
-
opts
captureRejections
- 型別:Boolean
- 描述:是否開啟自動捕獲非同步監聽器的 rejection
- false「預設」不開啟
- true 開啟
靜態屬性
captureRejectionSymbol
用來自定義非同步監聽器 rejection 的處理方法
EventEmitter.captureRejectionSymbol = kRejection;
errorMonitor
事件名稱
在該事件上註冊的監聽器,只監聽error
事件,且在常規error
事件監聽器呼叫之前被呼叫,不消耗 error
EventEmitter.errorMonitor = kErrorMonitor;
captureRejections
是否自動捕獲非同步監聽器的rejection
ObjectDefineProperty(EventEmitter, 'captureRejections', {
get() {
return EventEmitter.prototype[kCapture]; // 返回原型上的 kCapture
},
set(value) {
if (typeof value !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE('EventEmitter.captureRejections',
'boolean', value);
}
EventEmitter.prototype[kCapture] = value; // 設定原型上的 kCapture
},
enumerable: true
});
defaultMaxListeners
單個事件預設最大可註冊監聽器個數
- 預設情況下,每個事件可以最多註冊
10
個監聽器 - 可以使用
EventEmitter.defaultMaxListeners
屬性改變所有EventEmitter
例項的預設值(包括之前建立的)。 如果此值不是一個正數,則丟擲RangeError
let defaultMaxListeners = 10;
ObjectDefineProperty(EventEmitter, 'defaultMaxListeners', {
enumerable: true,
get: function() {
return defaultMaxListeners;
},
set: function(arg) {
if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
throw new ERR_OUT_OF_RANGE('defaultMaxListeners',
'a non-negative number',
arg);
}
defaultMaxListeners = arg;
}
});
靜態方法
init
例項初始化
EventEmitter.init = function(opts) {
// new EventEmitter(),在init方法執行之前,先建立了一個物件,this就指向該物件
if (this._events === undefined ||
this._events === ObjectGetPrototypeOf(this)._events) { // 避免給原型新增`_events`屬性,導致所有例項共享
this._events = ObjectCreate(null); // 純粹的鍵值對儲存物件,沒有原型
this._eventsCount = 0;
}
// 如果原型上有_maxListener則新例項繼承(_events不可以繼承,_maxListener可以繼承)
this._maxListeners = this._maxListeners || undefined;
if (opts?.captureRejections) {
if (typeof opts.captureRejections !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE('options.captureRejections',
'boolean', opts.captureRejections);
}
this[kCapture] = Boolean(opts.captureRejections);
} else {
// Assigning the kCapture property directly saves an expensive
// prototype lookup in a very sensitive hot path.
this[kCapture] = EventEmitter.prototype[kCapture]; // 預設值「false」
}
};
setMaxListeners
EventEmitter.setMaxListeners = function(n = defaultMaxListeners, ...eventTargets) {
if (typeof n !== 'number' || n < 0 || NumberIsNaN(n))
throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n);
if (eventTargets.length === 0) {
defaultMaxListeners = n;
} else {
if (isEventTarget === undefined)
isEventTarget = require('internal/event_target').isEventTarget;
for (let i = 0; i < eventTargets.length; i++) {
const target = eventTargets[i];
if (isEventTarget(target)) {
target[kMaxEventTargetListeners] = n;
target[kMaxEventTargetListenersWarned] = false;
} else if (typeof target.setMaxListeners === 'function') {
target.setMaxListeners(n);
} else {
throw new ERR_INVALID_ARG_TYPE(
'eventTargets',
['EventEmitter', 'EventTarget'],
target);
}
}
}
};
示例
const { EventEmitter } = require('events');
const ee = new EventEmitter();
console.log(ee.getMaxListeners());
EventEmitter.setMaxListeners(5, ee) // 可以用其靜態方法修改某個ee的最大監聽器個數
console.log(ee.getMaxListeners());
原型屬性
Symbol('kCapture')
在原型和例項上各有一份,節省到原型上查詢的開銷
是否捕獲非同步監聽器的rejection
const kCapture = Symbol('kCapture');
ObjectDefineProperty(EventEmitter.prototype, kCapture, {
value: false,
writable: true,
enumerable: false
});
// 獲取方式
const ee = new EventEmitter();
console.log('ee.kCapture = ', ee[Reflect.ownKeys(ee)[3]]); // false
例項屬性
Symbol('kCapture')
在原型和例項上各有一份,節省到原型上查詢的開銷
是否捕獲非同步監聽器的rejection
// init 方法中
// Assigning the kCapture property directly saves an expensive
// prototype lookup in a very sensitive hot path.
this[kCapture] = EventEmitter.prototype[kCapture]; // 預設值「false」
_events
儲存 監聽事件 和 註冊在該事件上的監聽器
EventEmitter.prototype._events = undefined;
_eventsCount
當前例項中監聽事件的個數
EventEmitter.prototype._eventsCount = 0;
_maxListeners
當前例項單個事件最大可註冊監聽器個數
EventEmitter.prototype._maxListeners = undefined;
例項方法
新增
on
同 addListener
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
addListener
把監聽器新增到指定事件監聽器陣列的末尾,多次新增相同的監聽器會多次呼叫
返回例項的引用,以便可以鏈式呼叫
EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false); // 原型上的方法提出去,將this傳入即可
};
function _addListener(target, type, listener, prepend) {
let m;
let events;
let existing;
checkListener(listener); // 檢測listener是否為function,否則拋異常
events = target._events;
if (events === undefined) { // 未初始化
events = target._events = ObjectCreate(null);
target._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (events.newListener !== undefined) { // 是否監聽 `newListener` 事件,每次註冊監聽器都會觸發,類似鉤子
target.emit('newListener', type,
listener.listener ? listener.listener : listener);
// Re-assign `events` because a newListener handler could have caused the
// this._events to be assigned to a new object
events = target._events; // 重新賦值
}
existing = events[type];
}
if (existing === undefined) { // 之前沒有監聽該事件
// Optimize the case of one listener. Don't need the extra array object.
events[type] = listener; //events 和 target._events 指向同一個物件
++target._eventsCount;
} else { // 之前已經監聽該事件
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = events[type] =
prepend ? [listener, existing] : [existing, listener];
// If we've already got an array, just append.
} else if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
// Check for listener leak 檢測最大監聽器個數
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true; // 是否多餘?
// No error code for this since it is a Warning
// eslint-disable-next-line no-restricted-syntax
const w = new Error('Possible EventEmitter memory leak detected. ' +
`${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. Use ` +
'emitter.setMaxListeners() to increase limit');
w.name = 'MaxListenersExceededWarning';
w.emitter = target;
w.type = type;
w.count = existing.length;
process.emitWarning(w); // 發出警告
}
}
return target;
}
prependListener
把監聽器新增到指定事件的監聽器陣列開頭
返回例項的引用,以便可以鏈式呼叫
EventEmitter.prototype.prependListener = function prependListener(type, listener) {
return _addListener(this, type, listener, true);
};
once
新增單次監聽器到指定事件的監聽器陣列末尾,觸發的時候先移除再執行
返回例項的引用,以便可以鏈式呼叫
function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn); // 先移除監聽器「同步操作」
this.fired = true; // 標記已觸發,保證只呼叫一次。【移除監聽器】和【保證只呼叫一次】是兩個分開的邏輯
if (arguments.length === 0) // 再呼叫監聽器
return this.listener.call(this.target);
return this.listener.apply(this.target, arguments);
}
}
function _onceWrap(target, type, listener) {
const state = { fired: false, wrapFn: undefined, target, type, listener };
const wrapped = onceWrapper.bind(state);
wrapped.listener = listener; // 掛載原始監聽器,一同傳遞給 removeListener 方法
state.wrapFn = wrapped; // 掛載包裹後的監聽器,用於移除
return wrapped;
}
EventEmitter.prototype.once = function once(type, listener) {
checkListener(listener);
this.on(type, _onceWrap(this, type, listener)); // 通過 once 監聽的事件獲取監聽器的方式為 listener.listener
return this;
};
prependOnceListener
新增單次監聽器到指定事件的監聽器陣列開頭
返回例項的引用,以便可以鏈式呼叫
EventEmitter.prototype.prependOnceListener = function prependOnceListener(type, listener) {
checkListener(listener);
this.prependListener(type, _onceWrap(this, type, listener));
return this;
};
移除
off
同 removeListener
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
removeListener
從指定事件的監聽器陣列中移除指定的監聽器
在事件觸發之後,最後一個監聽器執行完成之前, 移除監聽器不會影響已觸發的監聽器執行
返回例項的引用,以便可以鏈式呼叫
EventEmitter.prototype.removeListener = function removeListener(type, listener) {
checkListener(listener);
const events = this._events;
if (events === undefined) // 未初始化
return this;
const list = events[type];
if (list === undefined)
return this;
// 事件只有一個 listener,則會發生 list === listener( once 註冊的listener 為 list.listener)
if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0) // event例項只有一個監聽事件
this._events = ObjectCreate(null);
else { // event例項有多個監聽事件
delete events[type]; // 先移除後觸發
if (events.removeListener) // 如果監聽了 `removeListener` 事件,則觸發,類似鉤子
this.emit('removeListener', type, list.listener || listener);
}
} else if (typeof list !== 'function') { // events[type] 只可能是函式或陣列型別,這裡判斷非函式,則為陣列型別?
let position = -1; // 利用 `-1` 這個標誌
for (let i = list.length - 1; i >= 0; i--) {
if (list[i] === listener || list[i].listener === listener) {
position = i;
break;
}
}
if (position < 0)
return this;
if (position === 0)
list.shift();
else {
if (spliceOne === undefined)
spliceOne = require('internal/util').spliceOne; // 類似 Array#splice(),但是速度較快
spliceOne(list, position);
}
if (list.length === 1)
events[type] = list[0]; // 單個listener不用陣列
if (events.removeListener !== undefined)
this.emit('removeListener', type, listener);
}
return this;
};
removeAllListeners
移除所有監聽器或指定事件的所有監聽器
返回例項的引用,以便可以鏈式呼叫
EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
const events = this._events;
if (events === undefined) // 未初始化
return this;
// 邏輯1: 沒有註冊 removeListener 事件
// Not listening for removeListener, no need to emit
if (events.removeListener === undefined) {
if (arguments.length === 0) { // 等價於沒傳引數,type === undefined,刪除所有監聽事件
this._events = ObjectCreate(null);
this._eventsCount = 0;
} else if (events[type] !== undefined) {
if (--this._eventsCount === 0) // event例項只有一個監聽事件
this._events = ObjectCreate(null);
else
delete events[type];
}
return this;
}
// 邏輯2: 註冊了 removeListener 事件
// Emit removeListener for all listeners on all events
if (arguments.length === 0) { // 刪除所有監聽事件
for (const key of ReflectOwnKeys(events)) { // 較 Object#keys(),Reflect#ownKeys() 包含 Symbol 值的屬性名
if (key === 'removeListener') continue; // 把其他事件刪除後,再刪除。如果先刪除,後面的事件就走邏輯1了
this.removeAllListeners(key); // 遞迴,走邏輯3,然後返回
}
this.removeAllListeners('removeListener'); // 遞迴,走邏輯3,然後返回
this._events = ObjectCreate(null);
this._eventsCount = 0;
return this;
}
// 邏輯3: 真正刪除事件監聽器
const listeners = events[type];
if (typeof listeners === 'function') {
this.removeListener(type, listeners);
} else if (listeners !== undefined) {
// LIFO order
for (let i = listeners.length - 1; i >= 0; i--) {
this.removeListener(type, listeners[i]);
}
}
return this;
};
修改
setMaxListeners
修改當前 EventEmitter
例項單個事件最大監聽器個數。設為 Infinity
(或 0
)表示不限制監聽器的數量
返回例項的引用,以便可以鏈式呼叫
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {
throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n);
}
this._maxListeners = n;
return this;
};
getMaxListeners
返回當前EventEmitter
例項單個事件最大監聽器個數
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
return _getMaxListeners(this); // 把 this(例項)傳進去
};
function _getMaxListeners(that) {
if (that._maxListeners === undefined)
return EventEmitter.defaultMaxListeners;
return that._maxListeners;
}
獲取
listeners
返回一個陣列,包含指定事件的所有監聽器(拆包之後)
function _listeners(target, type, unwrap) {
const events = target._events;
if (events === undefined)
return [];
const evlistener = events[type];
if (evlistener === undefined)
return [];
if (typeof evlistener === 'function')
return unwrap ? [evlistener.listener || evlistener] : [evlistener];
return unwrap ?
unwrapListeners(evlistener) : arrayClone(evlistener);
}
EventEmitter.prototype.listeners = function listeners(type) {
return _listeners(this, type, true);
};
rawListeners
返回一個陣列,包含指定事件的所有(原始)監聽器
如果是通過 once 註冊的監聽器,則返回的是被包裝(wrap)後的監聽器
EventEmitter.prototype.rawListeners = function rawListeners(type) {
return _listeners(this, type, false);
};
listenerCount
返回指定事件上註冊的監聽器個數
EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
const events = this._events;
if (events !== undefined) {
const evlistener = events[type];
if (typeof evlistener === 'function') {
return 1;
} else if (evlistener !== undefined) {
return evlistener.length;
}
}
return 0;
}
eventNames
返回一個陣列,包含所有監聽事件。元素型別為 String 或 Symbol
EventEmitter.prototype.eventNames = function eventNames() { // 所有監聽事件
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
};
觸發
emit
觸發指定事件並同步呼叫註冊到該事件上的所有監聽器
如果事件有監聽器,則返回 true
,否則返回 false
EventEmitter.prototype.emit = function emit(type, ...args) {
let doError = (type === 'error'); // error 事件,特殊處理
const events = this._events;
if (events !== undefined) { // 有監聽事件
if (doError && events[kErrorMonitor] !== undefined) // 如果是 error 事件,且使用者新增了 kErrorMonitor 監聽器
this.emit(kErrorMonitor, ...args);
doError = (doError && events.error === undefined); // 如果是 error 事件,且沒有對應的 handler
} else if (!doError) // events === undefined && !doError => 未初始化且不是 error 事件,則返回 false
return false;
// 觸發 error 事件且沒有handler,丟擲異常(引數中的error例項或自己生成的error)
// If there is no 'error' event listener then throw.
if (doError) {
// 1. 有監聽事件 && 觸發error事件 && 沒有error事件的 handler
// 2. 未初始化(肯定也沒有error事件的 handler) && 觸發error事件
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) { // 第一個入參是 Error 例項
try { // 給 er 新增當前棧資訊(emit部分)
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
value: enhanceStackTrace.bind(this, er, capture),
configurable: true
});
} catch {}
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}
let stringifiedEr;
const { inspect } = require('internal/util/inspect');
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}
// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
throw err; // Unhandled 'error' event
}
const handler = events[type];
// 如果是 error 事件,至此一定有handler,如果是其他型別事件,仍然需要判斷handler的存在性
if (handler === undefined)
return false;
// 單個handler是函式,多個handler是陣列
if (typeof handler === 'function') {
const result = handler.apply(this, args); // 參見 https://github.com/nodejs/node/pull/38248
// We check if result is undefined first because that
// is the most common case so we do not pay any perf
// penalty
if (result !== undefined && result !== null) {
addCatch(this, result, type, args); // 如果是handler是非同步函式,捕獲其rejection異常
}
} else {
const len = handler.length; // 確定監聽器的個數,避免監聽器中再監聽同一個事件造成死迴圈
const listeners = arrayClone(handler); // 克隆handler陣列,防止在事件觸發後,已註冊監聽器的變動
for (let i = 0; i < len; ++i) {
const result = listeners[i].apply(this, args); // 同步依次呼叫
// We check if result is undefined first because that
// is the most common case so we do not pay any perf
// penalty.
// This code is duplicated because extracting it away
// would make it non-inlineable.
if (result !== undefined && result !== null) {
addCatch(this, result, type, args);
}
}
}
return true;
};
function addCatch(that, promise, type, args) {
if (!that[kCapture]) {
return;
}
// 符合 Promises/A+ 規範的 promise 的屬性 then 可能具有 get 方法,二次呼叫可能會報錯,因此採用call方式
// 因此下面採用的是 call 方式呼叫
// Handle Promises/A+ spec, then could be a getter
// that throws on second use.
try {
const then = promise.then;
if (typeof then === 'function') {
then.call(promise, undefined, function(err) {
// The callback is called with nextTick to avoid a follow-up // follow-up rejection ?
// rejection from this promise.
process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args);
});
}
} catch (err) {
that.emit('error', err);
}
}
function emitUnhandledRejectionOrErr(ee, err, type, args) {
if (typeof ee[kRejection] === 'function') { // 例項(使用者)自己實現了 kRejection 函式
ee[kRejection](err, type, ...args);
} else {
// We have to disable the capture rejections mechanism, otherwise
// we might end up in an infinite loop.
// 防止 error 事件 handler 也丟擲 error,導致死迴圈。
// 先儲存之前的值,再關閉,如果程式未「退出」再恢復之前的值
const prev = ee[kCapture];
// If the error handler throws, it is not catcheable and it
// will end up in 'uncaughtException'. We restore the previous
// value of kCapture in case the uncaughtException is present
// and the exception is handled.
// 如果沒有自定義 rejected promise 的處理函式,則走 error 事件的處理函式
// 如果 error 事件 handler 也丟擲 error,則不會被捕獲,nodejs 會觸發 uncaughtException 事件並「退出」
// 如果使用者處理了 uncaughtException 事件(通過 process#on() 處理),則恢復 kRejection 的值
try {
ee[kCapture] = false;
ee.emit('error', err);
} finally {
ee[kCapture] = prev;
}
}
}
其他
primordials
primordials物件用來保證內建模組可以訪問真正的不受使用者干擾的全域性變數
spliceOne
更高效的移除陣列中的元素
function spliceOne(list, index) {
for (; index + 1 < list.length; index++)
list[index] = list[index + 1]; // 從被移除元素開始,將後面的元素值依次向前複製
list.pop(); // 移除最後一個元素
}
建立例項未初始化
'use strict';
const { EventEmitter, captureRejectionSymbol } = require('events');
function MyEventEmitter() {
// EventEmitter.call(this);
}
MyEventEmitter.prototype = EventEmitter.prototype;
MyEventEmitter.prototype.constructor = MyEventEmitter
const ee = new MyEventEmitter();
// 上述建立 EventEmitter 例項的方式並沒有呼叫 EventEmitter.init() 初始化,因此例項屬性值都是 undefined
console.log('ee._events = ', ee._events);
console.log('ee._eventsCount = ', ee._eventsCount);
ee.on('test', () => { console.log('tttttttt') });
ee.emit('test');
console.log('ee.eventNames = ',ee.eventNames());