觀察者模式在前端開發中非常常用,我們經常用的事件就是觀察者模式的一種體現。它對我們解耦模塊、開發基於消息的業務起着非常重要的作用。Node.js 原生自帶 EventEmitter 模塊,可見它的重要性。
作為在工作中經常遇到和面試的經典題目當然要琢磨透徹,下面一步步手動寫一個eventEmitter
首先,我們要知道EE的api是什么樣的
node的EventEmitter包含了很多常用的API,我們一一來介紹幾個實用的API.
方法名
|
方法描述
|
addListener(event, listener)
|
為指定事件添加一個監聽器到監聽器數組的尾部。
|
prependListener(event,listener)
|
與addListener相對,為指定事件添加一個監聽器到監聽器數組的頭部。
|
on(event, listener)
|
其實就是addListener的別名
|
once(event, listener)
|
為指定事件注冊一個單次監聽器,即 監聽器最多只會觸發一次,觸發后立刻解除該監聽器。
|
removeListener(event, listener)
|
移除指定事件的某個監聽器,監聽器必須是該事件已經注冊過的監聽器
|
off(event, listener)
|
removeListener的別名
|
removeAllListeners([event])
|
移除所有事件的所有監聽器, 如果指定事件,則移除指定事件的所有監聽器。
|
setMaxListeners(n)
|
默認情況下, EventEmitters 如果你添加的監聽器超過 10 個就會輸出警告信息。 setMaxListeners 函數用於提高監聽器的默認限制的數量。
|
listeners(event)
|
返回指定事件的監聽器數組。
|
emit(event, [arg1], [arg2], [...])
|
按參數的順序執行每個監聽器,如果事件有注冊監聽返回 true,否則返回 false。
|
現在,我們來主要實現on,emit,removeListener 等幾個api
每一個EventEmitter實例都有一個包含所有事件的對象_events,
事件的監聽和監聽事件的觸發,以及監聽事件的移除等都在這個對象_events的基礎上實現。
首先實現一個EventEmitter類
class EventEmitter {
constructor() {
this._events = Object.create(null); // 定義事件的存儲對象
this._eventsCount = 0;
}
}
接着實現添加事件監聽的on方法:
-
為每個事件名稱添加一個數組作為事件的存儲器
-
事件由一個對象來保存,為了區別開on和once需要為每個事件增加一個isOnce字段
//添加事件監聽
on(eventName, fn, isOnce = false) {
if (typeof fn !== "function") {
throw new TypeError("The listener must be a function!");
}
if (!this._events[eventName]) {
this._events[eventName] = [];
this._events[eventName].push({ fn, isOnce });
} else {
this._events[eventName].push({ fn, isOnce }); // 存入監聽的事件名和事件
}
}
實現事件觸發方法emit:
-
找到事件名稱下面的所有事件以此觸發
-
當事件的isOnce 為true的時候在觸發事件之后清空事件
// 事件觸發
emit(eventName, ...args) {
if (!this._events[eventName]) {
return false;
}
const len = this._events[eventName].length;
for (let i = 0; i < len; i++) {
let event = this._events[eventName][i];
event.fn.call(this, ...args);
if (event.isOnce) {
this.removeListener(eventName, event.fn);
i--;
}
}
}
事件監聽解綁
1.清空事件名下的監聽的函數,函數為undefined則清空事件名下所有事件
// 移除監聽事件
removeListener(eventName, fn) {
if (!this._events[eventName]) return this;
if (!fn) {
delete this._events[eventName];
return this;
} else {
this._events[eventName].forEach((item, index) => {
if (item.fn === fn) {
this._events[eventName].splice(index, 1);
} else {
return this;
}
});
}
}
全部代碼實現
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>手動實現一個eventEmitter</title>
</head>
<body>
<script>
class EventEmitter {
constructor() {
this._events = Object.create(null); // 定義事件的存儲對象
this._eventsCount = 0;
}
//添加事件監聽
on(eventName, fn, isOnce = false) {
if (typeof fn !== "function") {
throw new TypeError("The listener must be a function!");
}
if (!this._events[eventName]) {
this._events[eventName] = [];
this._events[eventName].push({ fn, isOnce });
} else {
this._events[eventName].push({ fn, isOnce }); // 存入監聽的事件名和事件
}
}
//一次性事件監聽
once(eventName, fn) {
this.on(eventName, fn, true);
}
// 事件觸發
emit(eventName, ...args) {
if (!this._events[eventName]) {
return false;
}
const len = this._events[eventName].length;
for (let i = 0; i < len; i++) {
let event = this._events[eventName][i];
event.fn.call(this, ...args);
if (event.isOnce) {
this.removeListener(eventName, event.fn);
i--;
}
}
}
// 移除監聽事件
removeListener(eventName, fn) {
if (!this._events[eventName]) return this;
if (!fn) {
delete this._events[eventName];
return this;
} else {
this._events[eventName].forEach((item, index) => {
if (item.fn === fn) {
this._events[eventName].splice(index, 1);
} else {
return this;
}
});
}
}
// off:removeListener 的別名
off(eventName, fn) {
this.removeListener(eventName, fn);
}
// 移除所有監聽事件
removeAllListener(eventName) {
if (eventName) {
if (this._events[eventName]) {
this._events[eventName].length = 0;
}
} else {
this._events = Object.create(null);
}
}
}
function add(...args) {
let num = 0;
for (let i = 0; i < args.length; i++) {
num += args[i];
}
console.log(num);
return num;
}
let event = new EventEmitter();
// event.on("adding", add);
// event.emit("adding", 1, 2, 3, 4);
// event.removeListener("adding", add);
// event.emit("adding", 1, 2, 3, 4);
// event.once("adding", add);
event.emit("adding", 1, 2, 3, 4);
event.emit("adding", 1, 2, 3, 4);
</script>
</body>
</html>