ホームページ >ウェブフロントエンド >jsチュートリアル >Node.js の EventEmitter モジュールについて説明する記事
EventEmitter は Node.js の組み込みモジュールであり、イベント サブスクリプション メカニズムを提供します。次の記事では、Node.js の EventEmitter モジュールを理解し、その使用方法を紹介します。
EventEmitter は、events
モジュールを導入することで使用できるイベント サブスクリプション メカニズムを提供します。
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); // 监听 data 事件 eventEmitter.on("data", () => { console.log("data"); }); // 触发 data 事件 eventEmitter.emit("data");
上記のコードでは、on
メソッドを使用してコールバック関数をイベントにバインドし、emit
メソッドを使用してイベントをトリガーします。
on
メソッドと addListener
メソッドを使用してイベントのリスナーを追加できます。どちらの使用方法も同じです
eventEmitter.on("data", () => { console.log("data"); }); eventEmitter.addListener("data", () => { console.log("data"); });
最初のパラメータはイベント名で、2 番目のパラメータは対応するコールバック関数です。EventEmitter インスタンス オブジェクトが emit
を呼び出して対応するイベントをトリガーすると、コールバック関数が呼び出されます。
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("data"); }); eventEmitter.addListener("data", () => { console.log("data"); }); eventEmitter.emit("data");
のように、コンソールに 2 回出力されます data
data data
上記の例からわかるように、複数のコールバック関数を同じイベントです。
on
または addListener
を使用して複数のコールバック関数をバインドする場合、トリガーの順序は加算の順序になります。たとえば、
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("data 1"); }); eventEmitter.on("data", () => { console.log("data 2"); }); eventEmitter.on("data", () => { console.log("data 3"); }); eventEmitter.emit("data");
は
data 1 data 2 data 3
が繰り返し追加され、イベントが on## を使用してバインドされている場合、 # メソッドでは実行されません。再確認してください
const {EventEmitter} = require('events'); const eventEmitter = new EventEmitter(); const listener = () => { console.log("lsitener"); } eventEmitter.on("data", listener); eventEmitter.on("data", listener); eventEmitter.emit("data");コンソールに出力される結果は
lsitener lsitenerです。 上記のプログラムはイベントを
listener 関数に 2 回バインドしますただし、このコールバック関数を追加して重複を削除したため、リスナーがコンソールに 2 回出力されたかどうかは内部的にはチェックされません。
emit を通じてイベントがトリガーされたときに渡されるパラメータ (
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", data => { console.log(data); }); // 为回调函数传入参数 HelloWorld! eventEmitter.emit("data", "HelloWorld!");# など) を受け取ることもできます。 emit
では ## を使用します。 イベントがトリガーされると、追加のパラメーターが渡され、それがコールバック関数に渡されます。 同期実行
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("触发了 data 事件!"); }); console.log("start"); eventEmitter.emit("data"); console.log("end");
上記では、イベントの前後の両方で実行しました。イベントが非同期でトリガーされた場合、後続の print ステートメントが最初に実行されます。それ以外の場合、同期の場合は、イベントにバインドされたコールバック関数が最初に実行されます。実行結果は以下の通りです
start 触发了 data 事件! end
イベントトリガーが同期的に実行されていることがわかります。
off、removeListener
off および removeListener
メソッドは、on
および addLsitener## と同じ効果があります。 # これらの関数は逆です。それらの関数は、特定のイベントに対応するコールバック関数を削除することです。
const {EventEmitter} = require('events'); const eventEmitter = new EventEmitter(); let listener1 = () => { console.log("listener1"); } let listener2 = () => { console.log("listener2"); } eventEmitter.on("data", listener1); eventEmitter.on("data", listener2); // 第一次触发,两个回调函数否会执行 eventEmitter.emit("data"); eventEmitter.off("data", listener1); // 第二次触发,只会执行 listener2 eventEmitter.emit("data");
コンソールの出力結果は
listener1 listener2 listener2イベントが初めてトリガーされると、両方の関数が実行されます。次に、イベントのコールバック関数listener1を削除したので、2回目にトリガーされるときはlistener2のみがトリガーされます。
注:
onまたは一度だけ実行されるコールバック関数をバインドするには、addListener
を使用して匿名関数をバインドする場合、
offと
を渡すことはできません。 RemoveListenerコールバック関数のバインドを解除します。2 つの関数の参照が同じかどうかを比較することで関数のバインドが解除されます。
once
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.once("data", () => { console.log("data"); }); eventEmitter.emit("data"); eventEmitter.emit("data");
上記のコードでは、
once を使用してコールバック関数を data イベントにバインドし、次に
emit メソッドを使用してそれを 2 回トリガーします。
once を使用すると、バインドされたコールバック関数は 1 回だけトリガーされるため、2 回目にトリガーされるとコールバック関数は実行されず、データはコンソールに 1 回だけ出力されます。
さらに、
on
emit メソッド
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.once("data", data => { console.log(data); }); eventEmitter.emit("data", "Hello");
を通じてコールバック関数にパラメータを渡すこともできます。コンソールに結果が出力されます。
HelloprependListener、prependOnceListenerUse
addListener イベントにバインドされたコールバック関数は、
prependLsitener を使用しているときの加算順序です。バインドされたイベント コールバック関数は、他のコールバック関数の前に実行されます。
const {EventEmitter} = require('events'); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("on"); }); eventEmitter.prependListener("data", () => { console.log("prepend"); }); eventEmitter.emit("data");
まず、コンソールを使用して、上記の結果を
prepend onとして出力します。
prependOnceListener
prependListener と同じですが、バインドされたコールバック関数は 1 回だけ実行されます
const {EventEmitter} = require('events'); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("on"); }); eventEmitter.prependOnceListener("data", () => { console.log("prepend once"); }); eventEmitter.emit("data"); eventEmitter.emit("data");
上記では、
prependOnceListener を使用してコールバックをバインドしましたイベントがトリガーされると、このコールバック関数は他の関数の前に実行され、一度だけ実行されるため、関数を 2 回目にトリガーするとき、コールバック関数は実行されず、コンソールの出力結果は # になります。 ##prepend once on on
removeAllListeners([event])
方法可以删除事件 event
绑定的所有回调函数,如果没有传入 event
参数的话,那么该方法就会删除所有事件绑定的回调函数
const {EventEmitter} = require('events'); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("data 1"); }); eventEmitter.on("data", () => { console.log("data 2"); }); eventEmitter.emit("data"); eventEmitter.removeAllListeners("data"); eventEmitter.emit("data");
上面程序为 data
事件绑定了两个回调函数,并且在调用 removeAllListeners
方法之前分别触发了一次 data
事件,第二次触发 data
事件时,不会有任何的回调函数被执行,removeAllListeners
删除了 data
事件绑定的所有回调函数。控制台的打印结果为:
data 1 data 2
通过 eventNames
方法我们可以知道为哪些事件绑定了回调函数,它返回一个数组
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("start", () => { console.log("start"); }); eventEmitter.on("end", () => { console.log("end"); }); eventEmitter.on("error", () => { console.log("error"); }); console.log(eventEmitter.eventNames()); // [ 'start', 'end', 'error' ]
如果我们将某事件的所有回调函数删除后,此时 eventNames
便不会返回该事件了
eventEmitter.removeAllListeners("error"); console.log(eventEmitter.eventNames()); // [ 'start', 'end' ]
listenerCount
方法可以得到某个事件绑定了多少个回调函数
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { }); eventEmitter.on("data", () => { }); console.log(eventEmitter.listenerCount("data")); // 2
setMaxListeners
是用来设置最多为每个事件绑定多少个回调函数,但是实际上是可以绑定超过设置的数目的回调函数的,不过当你绑定超过指定数目的回调函数时,会在控制台给出一个警告
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); // 设置只能为每个回调函数绑定 1 个回调函数 eventEmitter.setMaxListeners(1); // 为 data 事件绑定了三个回调函数 eventEmitter.on("data", () => { console.log("data 1"); }); eventEmitter.on("data", () => { console.log("data 2"); }); eventEmitter.on("data", () => { console.log("data 3"); });
运行上述程序,控制台打印结果为
data 1 data 2 data 3 (node:36928) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 2 data listeners added to [EventEmitter]. Use emitter.setMaxListeners() to increase limit
可见事件绑定的三个回调函数都可以被触发,并且在控制台打印出了一条警告信息。
getMaxListeners
是获得能为每个事件绑定多少个回调函数的方法,使用 setMaxListeners
设置的值时多少,返回的值就是多少
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.setMaxListeners(1); console.log(eventEmitter.getMaxListeners()); // 1
如果没有使用 setMaxLsiteners
进行设置,那么默认能够为每个事件最多绑定 10
个回调函数,可以通过 EventEmitter
的 defaultMaxListeners
属性获得该值
const {EventEmitter} = require("events"); console.log(EventEmitter.defaultMaxListeners); // 10
当我们使用 once
绑定一个回调函数时,不会直接为该事件绑定该函数,而是会使用一个函数包装该函数,这个包装函数称为 wrapper
,然后为该事件绑定 wrapper
函数,在 wrapper
函数内部,设定了当执行一次之后将自己解绑的逻辑。
listeners
返回指定事件绑定的回调函数组成的数组,而 rawListeners
也是返回指定事件绑定的回调函数组成的数组,与 listeners
不同的是,对于 once
绑定的回调函数返回的是 wrapper
,而不是原生绑定的函数。
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.once("data", () => { console.log("once"); }) let fns = eventEmitter.listeners("data"); // once 绑定的函数,不是 wrapper,内部没有解绑的逻辑,所以后面触发 data 事件时还会执行 once 绑定的函数 fns[0]() eventEmitter.emit("data");
控制台打印结果为
once once
下面将上面的 listeners
替换为 rawListeners
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.once("data", () => { console.log("once"); }) let fns = eventEmitter.rawListeners("data"); // 因为返回的是 once 绑定函数的 wrapper,其内部有执行一次后解绑的逻辑 // 所以后面触发事件时 once 绑定的函数不会再执行 fns[0]() eventEmitter.emit("data");
控制台的打印结果为
once
在这个小节将从零实现一个 EventEmitter,来加深对该模块的理解。首先我们需要准备一个 listeners
来存储所有绑定的回调函数,它是一个 Map
对象,键是事件名,而值是一个数组,数组中保存的是该事件绑定的回调函数。
class EventEmitter { constructor() { this.listeners = new Map(); } }
使用 on
绑定回调函数时,我们先判断 Map
集合中是否有为该事件绑定回调函数,如果有取出对应数组,并添加该回调函数进数组,没有则新建一个数组,添加该回调函数,并添加进 Map
集合
on(event, callback) { if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); fns.push(callback); }
addListener
的功能与 on
是一样的,我们直接调用 on
方法即可
addListener(event, callback) { this.on(event, callback); }
当我们使用 emit
触发事件时,我们从 Map
取出对应的回调函数组成的数组,然后依次取出函数执行。另外我们还可以通过 emit
传递参数
emit(event, ...args) { if(!this.listeners.has(event)) { return; } let fns = this.listeners.get(event); let values = []; for(let fn of fns) { values.push(fn); } for (let fn of values) { fn(...args); } }
这里你可能会觉得我写的有点复杂,所以你会觉得直接这么写更好
emit(event, ...args) { if(!this.listeners.has(event)) { return; } for (let fn of fns) { fn(...args); } }一开始我也是这么写的,但是因为
once
绑定的函数它在执行完毕后将自己从数组中移除,并且是同步的,所以在执行循环的时候,数组是在不断变化的,使用上述的方式会使得一些回调函数会被漏掉,所以我才会先将数组中的函数复制到另一个数组,然后遍历这个新的数组,因为once
绑定的函数它只会删除原数组中的函数,而不会删除新的这个数组,所以新数组的长度在遍历的过程不会改变,也就不会发生漏掉函数未执行的情况。
实现 prependListener
的逻辑同 on
一样,不过我们是往数组的最前方添加回调函数
prependListener(event, callback) { if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); fns.unshift(callback); }
使用 off
方法是用来解绑事件的,在数组中找到指定的函数,然后删除即可
off(event, callback) { if(!this.listeners.has(event)) { return; } let fns = this.listeners.get(event); // 找出数组中的回调函数,然后删除 for (let i = 0; i < fns.length; i++) { if(fns[i] === callback) { fns.splice(i, 1); break; } } // 如果删除回调函数后,数组为空,则删除该事件 if (fns.length === 0) { this.listeners.delete(event); } }
removeListener
同 off
的作用一样,我们在内部直接调用 off
方法即可
removeListener(event, callback) { this.off(event, callback); }
使用 once
绑定一个只执行一次的函数,所以我们需要将绑定的回调函数使用一个函数包装一下,然后添加进数组中,这个包装函数我们称之为 wrapper
。在包装函数中,当执行一遍后会将自己从数组中删除
once(event, callback) { let wrapper = (...args) => { callback(...args); this.off(event, wrapper); } if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); fns.push(wrapper); }
prependOnceListener
的实现同 once
,只是向数组的开头插入函数,将上面代码中的 push
换为 unshift
即可
prependOnceListener(event, callback) { let wrapper = (...args) => { callback(...args); this.off(event, wrapper); } if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); fns.unshift(wrapper); }
直接从删除对应的事件,如果没有传入具体事件的话,则需要删除所有的事件
removeAllListeners(event) { // 如果没有传入 event,则删除所有事件 if (event === undefined) { this.listeners = new Map(); return; } this.listeners.delete(event); }
获得已经绑定了哪些事件
eventNames() { return [...this.listeners.keys()]; }
获得某事件绑定可多少个回调函数
listenerCount(event) { return this.listeners.get(event).length; }
上述的实现有一个 bug,那就是无法删除使用
once
绑定的函数,我的想法是使用一个Map
将once
绑定的函数同对应的wrapper
对应,删除时即可根据once
的回调函数找到对应的wrapper
然后删除constructor() { this.listeners = new Map(); // 保存 once 的回调函数与对应的 wrapper this.onceToWrapper = new Map(); } once(event, callback) { let wrapper = (...args) => { callback(...args); // 删除之前,删除 callback 和 wrapper 的关系 this.onceToWrapper.delete(callback); this.off(event, wrapper); } if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); // 添加之前,绑定 callback 和 wrapper 的关系 this.onceToWrapper.set(callback, wrapper); fns.push(wrapper); } prependOnceListener(event, callback) { let wrapper = (...args) => { callback(...args); // 同上 this.onceToWrapper.delete(callback); this.off(event, wrapper); } if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); // 同上 this.onceToWrapper.set(callback, wrapper); fns.unshift(wrapper); } off(event, callback) { if(!this.listeners.has(event)) { return; } let fns = this.listeners.get(event); // 先从 onceToWrapper 中查找是否有对应的 wrapper,如果有说明是 once 绑定的 callback = this.onceToWrapper.get(callback) || callback; for (let i = 0; i < fns.length; i++) { if(fns[i] === callback) { fns.splice(i, 1); break; } } if (fns.length === 0) { this.listeners.delete(event); } }
全部代码如下
class EventEmitter { constructor() { this.listeners = new Map(); this.onceToWrapper = new Map(); } on(event, callback) { if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); fns.push(callback); } addListener(event, callback) { this.on(event, callback); } emit(event, ...args) { if(!this.listeners.has(event)) { return; } let fns = this.listeners.get(event); let values = []; for(let fn of fns) { values.push(fn); } for (let fn of values) { fn(...args); } } prependListener(event, callback) { if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); fns.unshift(callback); } off(event, callback) { if(!this.listeners.has(event)) { return; } let fns = this.listeners.get(event); callback = this.onceToWrapper.get(callback) || callback; for (let i = 0; i < fns.length; i++) { if(fns[i] === callback) { fns.splice(i, 1); break; } } if (fns.length === 0) { this.listeners.delete(event); } } removeListener(event, callback) { this.off(event, callback); } once(event, callback) { let wrapper = (...args) => { callback(...args); this.onceToWrapper.delete(callback); this.off(event, wrapper); } if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); this.onceToWrapper.set(callback, wrapper); fns.push(wrapper); } prependOnceListener(event, callback) { let wrapper = (...args) => { callback(...args); this.onceToWrapper.delete(callback); this.off(event, wrapper); } if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); this.onceToWrapper.set(callback, wrapper); fns.unshift(wrapper); } removeAllListeners(event) { if (event === undefined) { this.listeners = new Map(); return; } this.listeners.delete(event); } eventNames() { return [...this.listeners.keys()]; } listenerCount(event) { return this.listeners.get(event).length; } }
更多node相关知识,请访问:nodejs 教程!!
以上がNode.js の EventEmitter モジュールについて説明する記事の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。