Heim > Web-Frontend > js-Tutorial > Ein Artikel über das EventEmitter-Modul in Node.js

Ein Artikel über das EventEmitter-Modul in Node.js

青灯夜游
Freigeben: 2021-12-21 18:59:41
nach vorne
1697 Leute haben es durchsucht

EventEmitter ist ein integriertes Modul von Node.js, das uns einen Ereignisabonnementmechanismus bietet. Der folgende Artikel wird Ihnen das EventEmitter-Modul in Node.js näher bringen und seine Verwendung vorstellen. Ich hoffe, er wird Ihnen hilfreich sein!

Ein Artikel über das EventEmitter-Modul in Node.js

Verwendung von EventEmitter

EventEmitter bietet uns einen Mechanismus zum Abonnieren von Ereignissen, der durch die Einführung des Moduls events verwendet werden kann. events 模块来使用它。

const {EventEmitter} = require("events");
const eventEmitter = new EventEmitter();

// 监听 data 事件
eventEmitter.on("data", () => {
    console.log("data");
});

// 触发 data 事件
eventEmitter.emit("data");
Nach dem Login kopieren

上述代码我们使用 on 方法来为事件绑定回调函数,使用 emit 方法来触发一个事件。

on、addListener

我们可以通过 onaddListener 方法来为某事件添加一个监听器,二者的使用是一样

eventEmitter.on("data", () => {
    console.log("data");
});

eventEmitter.addListener("data", () => {
    console.log("data");
});
Nach dem Login kopieren

第一个参数为事件名,第二个参数为对应的回调函数,当 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");
Nach dem Login kopieren

在控制台会打印出两次 data

data
data
Nach dem Login kopieren

从上面的例子也可以看出,可以为同一事件绑定多个回调函数。

执行顺序

当使用 onaddListener 绑定多个回调函数时,触发的顺序就是添加的顺序,如

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");
Nach dem Login kopieren

会在控制台依次打印出

data 1
data 2
data 3
Nach dem Login kopieren

重复添加

并且使用 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");
Nach dem Login kopieren

控制台的打印结果为

lsitener
lsitener
Nach dem Login kopieren

上面的程序为事件绑定了两次 listener 这个函数,但是内部并不会检查是否已经添加过这个回调函数,然后去重,所以上面在控制台打印出了两次 listener。

传递参数

另外回调函数还可以接收参数,参数通过 emit 触发事件时传入,如

const {EventEmitter} = require("events");
const eventEmitter = new EventEmitter();

eventEmitter.on("data", data => {
    console.log(data);
});

// 为回调函数传入参数 HelloWorld!
eventEmitter.emit("data", "HelloWorld!");
Nach dem Login kopieren

上面我们使用 emit 触发事件时,还传递了额外的参数,这个参数会被传递给回调函数。

同步执行

另外一个比较关心的问题,事件的触发是同步的还是异步的,我们做一个实验

const {EventEmitter} = require("events");
const eventEmitter = new EventEmitter();

eventEmitter.on("data", () => {
    console.log("触发了 data 事件!");
});

console.log("start");
eventEmitter.emit("data");
console.log("end");
Nach dem Login kopieren

上面我们我们在触发事件前后都向控制台打印了信息,如果触发事件后是异步执行的,那么后面的打印语句就会先执行,否则如果是同步的话,就会先执行事件绑定的回调函数。执行结果如下

start
触发了 data 事件!
end
Nach dem Login kopieren

可见事件触发是同步执行的。

off、removeListener

offremoveListener 方法的作用同 onaddLsitener 的作用是相反的,它们的作用是为某个事件删除对应的回调函数

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");
Nach dem Login kopieren

控制台打印结果为

listener1
listener2
listener2
Nach dem Login kopieren

第一次触发事件时,两个事件都会触发,然后我们为事件删除了 listener1 这个回调函数,所以第二次触发时,只会触发 listener2。

注意:如果我们使用 on 或者 addListener 绑定的是一个匿名函数,那么便无法通过 offremoveListener 去解绑一个回调函数,因为它会通过比较两个函数的引用是否相同来解绑函数的。

once

使用 once 可以绑定一个只执行一次的回调函数,当触发一次之后,该回调函数便自动会被解绑

const {EventEmitter} = require("events");
const eventEmitter = new EventEmitter();

eventEmitter.once("data", () => {
    console.log("data");
});

eventEmitter.emit("data");
eventEmitter.emit("data");
Nach dem Login kopieren

上述代码我们使用 oncedata 事件绑定了一个回调函数,然后使用 emit 方法触发了两次,因为使用 once 绑定的回调函数只会被触发一次,所以第二次触发,回调函数不会执行,所以在控制台只打印了一次 data。

另外同 on 绑定的回调函数一样,我们同样可以通过 emit 方法向回调函数传递参数

const {EventEmitter} = require("events");
const eventEmitter = new EventEmitter();

eventEmitter.once("data", data => {
    console.log(data);
});

eventEmitter.emit("data", "Hello");
Nach dem Login kopieren

控制台打印结果

Hello
Nach dem Login kopieren

prependListener、prependOnceListener

使用 on 或者 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");
Nach dem Login kopieren

上述代打我们先用控制台的打印结果为

prepend
on
Nach dem Login kopieren

prependOnceListenerprependListener,不过它绑定的回调函数只会被执行一次

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");
Nach dem Login kopieren

上面我们使用 prependOnceListener

prepend once
on
on
Nach dem Login kopieren
Nach dem Login kopieren

Im obigen Code verwenden wir die Methode on, um eine Rückruffunktion an das Ereignis zu binden, und verwenden die Methode emit, um ein Ereignis auszulösen. 🎜

on, addListener

🎜Wir können einen Listener für ein Ereignis über die Methoden on und addListener hinzufügen. Die Verwendung beider ist gleich🎜
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");
Nach dem Login kopieren
Nach dem Login kopieren
🎜Der erste Parameter ist der Ereignisname und der zweite Parameter ist die entsprechende Rückruffunktion, die aufgerufen wird, wenn das EventEmitter-Instanzobjekt emit aufruft, um das entsprechende auszulösen Ereignis. Rückruffunktionen wie 🎜
data 1
data 2
Nach dem Login kopieren
Nach dem Login kopieren
🎜 geben data zweimal auf der Konsole aus🎜
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' ]
Nach dem Login kopieren
Nach dem Login kopieren
🎜Wie aus dem obigen Beispiel ersichtlich ist, können mehrere Rückruffunktionen an dasselbe Ereignis gebunden werden. 🎜

Ausführungsreihenfolge

🎜Bei Verwendung von on oder addListener zum Binden mehrerer Rückruffunktionen wird die Die Reihenfolge der Auslösung ist die Reihenfolge der Hinzufügung, zum Beispiel wird 🎜
eventEmitter.removeAllListeners("error");
console.log(eventEmitter.eventNames()); // [ 'start', 'end' ]
Nach dem Login kopieren
Nach dem Login kopieren
🎜 auf der Konsole nacheinander ausgedruckt: 🎜
const {EventEmitter} = require("events");
const eventEmitter = new EventEmitter();

eventEmitter.on("data", () => {

});
eventEmitter.on("data", () => {

});

console.log(eventEmitter.listenerCount("data")); // 2
Nach dem Login kopieren
Nach dem Login kopieren

Wiederholt hinzugefügt

🎜 und verwenden on</code > Wenn die Methode ein Ereignis bindet, führt sie keine Deduplizierungsprüfung durch , tut dies jedoch nicht intern. Es prüft nicht, ob diese Rückruffunktion hinzugefügt wurde, bevor sie dedupliziert wird, sodass der Listener zweimal auf der Konsole gedruckt wird. 🎜<h4 data-id="heading-4">Übergabe von Parametern</h4>🎜Darüber hinaus kann die Callback-Funktion auch Parameter empfangen. Die Parameter werden übergeben, wenn das Ereignis durch <code>emit ausgelöst wird , wie zum Beispiel 🎜
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");
});
Nach dem Login kopieren
Nach dem Login kopieren
🎜Was wir oben haben Wenn Sie emit verwenden, um ein Ereignis auszulösen, werden zusätzliche Parameter übergeben, die an die Rückruffunktion übergeben werden. 🎜

Synchronische Ausführung

🎜Ein weiteres Problem ist, ob das Ereignis synchron oder asynchron ausgelöst wird. Machen wir ein Experiment🎜
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
Nach dem Login kopieren
Nach dem Login kopieren
🎜Wir lösen das oben genannte Ereignis aus. Die Informationen werden gedruckt Wenn das Ereignis vorher und nachher asynchron ausgelöst wird, wird die nachfolgende Druckanweisung zuerst ausgeführt. Wenn es synchron ist, wird die an das Ereignis gebundene Rückruffunktion zuerst ausgeführt. Die Ausführungsergebnisse sind wie folgt🎜
const {EventEmitter} = require("events");
const eventEmitter = new EventEmitter();

eventEmitter.setMaxListeners(1);

console.log(eventEmitter.getMaxListeners()); // 1
Nach dem Login kopieren
Nach dem Login kopieren
🎜Es ist ersichtlich, dass die Ereignisauslösung synchron ausgeführt wird. 🎜

off, removeListener

🎜off hat den gleichen Effekt wie die removeListener-Methode on</code > Die Funktion von <code>addLsitener ist das Gegenteil. Ihre Funktion besteht darin, die entsprechende Rückruffunktion für ein bestimmtes Ereignis zu löschen Ereignisse werden ausgelöst, dann haben wir die Rückruffunktion listener1 für das Ereignis gelöscht, sodass beim zweiten Auslösen nur listener2 ausgelöst wird. 🎜
🎜Hinweis: Wenn wir on oder addListener verwenden, um eine anonyme Funktion zu binden, können wir off und removeListener nicht weitergeben um die Bindung einer Rückruffunktion aufzuheben, da die Bindung der Funktion dadurch aufgehoben wird, dass verglichen wird, ob die Referenzen der beiden Funktionen gleich sind. 🎜

once

🎜Verwenden Sie once, um eine Rückruffunktion zu binden, die nur einmal ausgeführt wird Rückruf Die Funktion wird automatisch entbunden🎜
const {EventEmitter} = require("events");

console.log(EventEmitter.defaultMaxListeners); // 10
Nach dem Login kopieren
Nach dem Login kopieren
🎜Im obigen Code verwenden wir once, um eine Rückruffunktion an das data-Ereignis zu binden, und verwenden dann emit -Methode Wird zweimal ausgelöst, da die mit once gebundene Rückruffunktion nur einmal ausgelöst wird. Beim zweiten Auslösen wird die Rückruffunktion nicht ausgeführt, sodass die Daten nur gedruckt werden einmal auf der Konsole. 🎜🎜Darüber hinaus können wir, genau wie die an on gebundene Callback-Funktion, auch Parameter über die emit-Methode an die Callback-Funktion übergeben🎜
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");
Nach dem Login kopieren
Nach dem Login kopieren
🎜Die Konsole druckt die Ergebnisse🎜
once
once
Nach dem Login kopieren
Nach dem Login kopieren

prependListener, prependOnceListener

🎜Verwenden Sie on oder addListener, um die Rückruffunktion an das Ereignis zu binden, das ausgeführt wird entsprechend der Reihenfolge des Hinzufügens und Die mit prependLsitener gebundene Ereignisrückruffunktion wird vor anderen Rückruffunktionen ausgeführt >prependOnceListener ist dasselbe wie prependListener, aber die daran gebundene Callback-Funktion wird nur einmal ausgeführt 🎜
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");
Nach dem Login kopieren
Nach dem Login kopieren
🎜 Oben haben wir prependOnceListener verwendet, um eine Callback-Funktion zu binden Wenn ein Ereignis ausgelöst wird, wird die Rückruffunktion ausgeführt. Andere Funktionen werden zuvor ausgeführt und nur einmal ausgeführt. Wenn wir die Funktion also zum zweiten Mal auslösen, wird die Rückruffunktion nicht ausgeführt und das Konsolendruckergebnis ist 🎜
prepend once
on
on
Nach dem Login kopieren
Nach dem Login kopieren

removeAllListeners

removeAllListeners([event]) 方法可以删除事件 event 绑定的所有回调函数,如果没有传入 event 参数的话,那么该方法就会删除所有事件绑定的回调函数

const {EventEmitter} = require(&#39;events&#39;);
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");
Nach dem Login kopieren
Nach dem Login kopieren

上面程序为 data 事件绑定了两个回调函数,并且在调用 removeAllListeners 方法之前分别触发了一次 data 事件,第二次触发 data 事件时,不会有任何的回调函数被执行,removeAllListeners 删除了 data 事件绑定的所有回调函数。控制台的打印结果为:

data 1
data 2
Nach dem Login kopieren
Nach dem Login kopieren

eventNames

通过 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()); // [ &#39;start&#39;, &#39;end&#39;, &#39;error&#39; ]
Nach dem Login kopieren
Nach dem Login kopieren

如果我们将某事件的所有回调函数删除后,此时 eventNames 便不会返回该事件了

eventEmitter.removeAllListeners("error");
console.log(eventEmitter.eventNames()); // [ &#39;start&#39;, &#39;end&#39; ]
Nach dem Login kopieren
Nach dem Login kopieren

listenerCount

listenerCount 方法可以得到某个事件绑定了多少个回调函数

const {EventEmitter} = require("events");
const eventEmitter = new EventEmitter();

eventEmitter.on("data", () => {

});
eventEmitter.on("data", () => {

});

console.log(eventEmitter.listenerCount("data")); // 2
Nach dem Login kopieren
Nach dem Login kopieren

setMaxLsiteners、getMaxListeners

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");
});
Nach dem Login kopieren
Nach dem Login kopieren

运行上述程序,控制台打印结果为

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
Nach dem Login kopieren
Nach dem Login kopieren

可见事件绑定的三个回调函数都可以被触发,并且在控制台打印出了一条警告信息。

getMaxListeners 是获得能为每个事件绑定多少个回调函数的方法,使用 setMaxListeners 设置的值时多少,返回的值就是多少

const {EventEmitter} = require("events");
const eventEmitter = new EventEmitter();

eventEmitter.setMaxListeners(1);

console.log(eventEmitter.getMaxListeners()); // 1
Nach dem Login kopieren
Nach dem Login kopieren

如果没有使用 setMaxLsiteners 进行设置,那么默认能够为每个事件最多绑定 10 个回调函数,可以通过 EventEmitterdefaultMaxListeners 属性获得该值

const {EventEmitter} = require("events");

console.log(EventEmitter.defaultMaxListeners); // 10
Nach dem Login kopieren
Nach dem Login kopieren

listeners、rawListeners

当我们使用 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");
Nach dem Login kopieren
Nach dem Login kopieren

控制台打印结果为

once
once
Nach dem Login kopieren
Nach dem Login kopieren

下面将上面的 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");
Nach dem Login kopieren
Nach dem Login kopieren

控制台的打印结果为

once
Nach dem Login kopieren

实现一个 EventEmitter

在这个小节将从零实现一个 EventEmitter,来加深对该模块的理解。首先我们需要准备一个 listeners 来存储所有绑定的回调函数,它是一个 Map 对象,键是事件名,而值是一个数组,数组中保存的是该事件绑定的回调函数。

class EventEmitter {
    constructor() {
        this.listeners = new Map();
    }
}
Nach dem Login kopieren

on、addListener

使用 on 绑定回调函数时,我们先判断 Map 集合中是否有为该事件绑定回调函数,如果有取出对应数组,并添加该回调函数进数组,没有则新建一个数组,添加该回调函数,并添加进 Map 集合

on(event, callback) {
    if(!this.listeners.has(event)) {
        this.listeners.set(event, []);
    }
    let fns = this.listeners.get(event);
    fns.push(callback);
}
Nach dem Login kopieren

addListener 的功能与 on 是一样的,我们直接调用 on 方法即可

addListener(event, callback) {
    this.on(event, callback);
}
Nach dem Login kopieren

emit

当我们使用 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);
    }
}
Nach dem Login kopieren

这里你可能会觉得我写的有点复杂,所以你会觉得直接这么写更好

emit(event, ...args) {
    if(!this.listeners.has(event)) {
        return;
    }
    for (let fn of fns) {
        fn(...args);
    }
}
Nach dem Login kopieren

一开始我也是这么写的,但是因为 once 绑定的函数它在执行完毕后将自己从数组中移除,并且是同步的,所以在执行循环的时候,数组是在不断变化的,使用上述的方式会使得一些回调函数会被漏掉,所以我才会先将数组中的函数复制到另一个数组,然后遍历这个新的数组,因为 once 绑定的函数它只会删除原数组中的函数,而不会删除新的这个数组,所以新数组的长度在遍历的过程不会改变,也就不会发生漏掉函数未执行的情况。

prependListener

实现 prependListener 的逻辑同 on 一样,不过我们是往数组的最前方添加回调函数

prependListener(event, callback) {
    if(!this.listeners.has(event)) {
        this.listeners.set(event, []);
    }
    let fns = this.listeners.get(event);
    fns.unshift(callback);
}
Nach dem Login kopieren

off、removeListener

使用 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);
    }
}
Nach dem Login kopieren

removeListeneroff 的作用一样,我们在内部直接调用 off 方法即可

removeListener(event, callback) {
    this.off(event, callback);
}
Nach dem Login kopieren

once、prependOnceListener

使用 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);
}
Nach dem Login kopieren

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);
}
Nach dem Login kopieren

removeAllListeners

直接从删除对应的事件,如果没有传入具体事件的话,则需要删除所有的事件

removeAllListeners(event) {
    // 如果没有传入 event,则删除所有事件
    if (event === undefined) {
        this.listeners = new Map();
        return;
    }
    this.listeners.delete(event);
}
Nach dem Login kopieren

eventNames

获得已经绑定了哪些事件

eventNames() {
    return [...this.listeners.keys()];
}
Nach dem Login kopieren

listenerCount

获得某事件绑定可多少个回调函数

listenerCount(event) {
    return this.listeners.get(event).length;
}
Nach dem Login kopieren

上述的实现有一个 bug,那就是无法删除使用 once 绑定的函数,我的想法是使用一个 Maponce 绑定的函数同对应的 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);
    }
}
Nach dem Login kopieren

全部代码如下

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;
    }
}
Nach dem Login kopieren

更多node相关知识,请访问:nodejs 教程!!

Das obige ist der detaillierte Inhalt vonEin Artikel über das EventEmitter-Modul in Node.js. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:juejin.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage