Home >Web Front-end >JS Tutorial >An article to talk about the EventEmitter module in Node.js
EventEmitter is a built-in module of Node.js, which provides us with an event subscription mechanism. The following article will take you to understand the EventEmitter module in Node.js and introduce its usage. I hope it will be helpful to you!
EventEmitter provides us with an event subscription mechanism, which can be used by introducing the events
module.
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); // 监听 data 事件 eventEmitter.on("data", () => { console.log("data"); }); // 触发 data 事件 eventEmitter.emit("data");
In the above code, we use the on
method to bind a callback function to the event, and the emit
method to trigger an event.
We can add a listener for an event through the on
and addListener
methods. The use of both is the same
eventEmitter.on("data", () => { console.log("data"); }); eventEmitter.addListener("data", () => { console.log("data"); });
The first parameter is the event name, and the second parameter is the corresponding callback function. When the EventEmitter instance object calls emit
to trigger the corresponding event, the callback function will be called, such as
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("data"); }); eventEmitter.addListener("data", () => { console.log("data"); }); eventEmitter.emit("data");
will be printed twice on the console data
data data
As can be seen from the above example, multiple callback functions can be bound to the same event. .
When using on
or addListener
to bind multiple callback functions, the order of triggering is the order of addition. For example,
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");
will be printed out on the console in sequence
data 1 data 2 data 3
is added repeatedly and the event is bound using the on
method, it will not be done. Re-check
const {EventEmitter} = require('events'); const eventEmitter = new EventEmitter(); const listener = () => { console.log("lsitener"); } eventEmitter.on("data", listener); eventEmitter.on("data", listener); eventEmitter.emit("data");
The printed result on the console is
lsitener lsitener
The above program binds the event twice to the listener
function, but it does not check internally whether it has been I added this callback function and then removed the duplicates, so the listener was printed twice on the console.
In addition, the callback function can also receive parameters, which are passed in when the event is triggered through emit
, such as
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", data => { console.log(data); }); // 为回调函数传入参数 HelloWorld! eventEmitter.emit("data", "HelloWorld!");
We use ## above #emit When the event is triggered, additional parameters are passed, which will be passed to the callback function.
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("触发了 data 事件!"); }); console.log("start"); eventEmitter.emit("data"); console.log("end");Above we have done both before and after the event is triggered. Information is printed to the console. If the event is triggered asynchronously, the subsequent print statement will be executed first. Otherwise, if it is synchronous, the callback function bound to the event will be executed first. The execution results are as follows
start 触发了 data 事件! endIt can be seen that event triggering is executed synchronously. off, removeListener
off and
removeListener methods have the same effect as
on and
addLsitener Their functions are opposite. Their function is to delete the corresponding callback function for a certain event.
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");The console print result is
listener1 listener2 listener2When the event is triggered for the first time, both events will be triggered. Then we deleted the callback function listener1 for the event, so when it is triggered for the second time, only listener2 will be triggered.
Note: If we useonceUseon
or
addListenerto bind an anonymous function, then we cannot pass
offand
removeListenerTo unbind a callback function, because it will unbind the function by comparing whether the references of the two functions are the same.
once to bind a callback function that is executed only once. When triggered once, the callback function will be automatically unbound
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.once("data", () => { console.log("data"); }); eventEmitter.emit("data"); eventEmitter.emit("data");In the above code, we use
once to bind a callback function to the
data event, and then use the
emit method to trigger it twice, because using
once The bound callback function will only be triggered once, so the second time it is triggered, the callback function will not be executed, so the data is only printed once on the console.
on, we can also pass parameters to the callback function through the
emit method
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.once("data", data => { console.log(data); }); eventEmitter.emit("data", "Hello");The console prints the results
HelloprependListener, prependOnceListenerUse
on or
addListener The callback functions bound to the event will be executed according to the order of addition, while using
prependLsitener The bound event callback function will be executed before other callback functions
const {EventEmitter} = require('events'); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("on"); }); eventEmitter.prependListener("data", () => { console.log("prepend"); }); eventEmitter.emit("data");We first use the console to print the above result as
prepend on
prependOnceListener Same as
prependListener, but the callback function bound to it will only be executed once
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");Above we used
prependOnceListener to bind a callback function when the event is triggered , this callback function will be executed before other functions, and will only be executed once, so when we trigger the function for the second time, the callback function will not be executed, and the console print result is
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 教程!!
The above is the detailed content of An article to talk about the EventEmitter module in Node.js. For more information, please follow other related articles on the PHP Chinese website!