Node.js의 EventEmitter 모듈에 대해 이야기하는 기사

青灯夜游
풀어 주다: 2021-12-21 18:59:41
앞으로
1572명이 탐색했습니다.

EventEmitter는 Node.js에 내장된 모듈로, 이벤트 구독 메커니즘을 제공합니다. 다음 글은 Node.js의 EventEmitter 모듈을 이해하고 사용법을 소개하는 데 도움이 되기를 바랍니다.

Node.js의 EventEmitter 모듈에 대해 이야기하는 기사

EventEmitter 사용

EventEmitter는 events 모듈을 도입하여 사용할 수 있는 이벤트 구독 메커니즘을 제공합니다. events 模块来使用它。

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

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

// 触发 data 事件
eventEmitter.emit("data");
로그인 후 복사

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

on、addListener

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

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

eventEmitter.addListener("data", () => {
    console.log("data");
});
로그인 후 복사

第一个参数为事件名,第二个参数为对应的回调函数,当 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");
로그인 후 복사

在控制台会打印出两次 data

data
data
로그인 후 복사

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

执行顺序

当使用 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");
로그인 후 복사

会在控制台依次打印出

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 这个函数,但是内部并不会检查是否已经添加过这个回调函数,然后去重,所以上面在控制台打印出了两次 listener。

传递参数

另外回调函数还可以接收参数,参数通过 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");
로그인 후 복사

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

start
触发了 data 事件!
end
로그인 후 복사

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

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");
로그인 후 복사

控制台打印结果为

listener1
listener2
listener2
로그인 후 복사

第一次触发事件时,两个事件都会触发,然后我们为事件删除了 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");
로그인 후 복사

上述代码我们使用 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");
로그인 후 복사

控制台打印结果

Hello
로그인 후 복사

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");
로그인 후 복사

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

prepend
on
로그인 후 복사

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");
로그인 후 복사

上面我们使用 prependOnceListener

prepend once
on
on
로그인 후 복사
로그인 후 복사

위 코드에서는 콜백 함수를 이벤트에 바인딩하기 위해 on 메서드를 사용하고 이벤트를 트리거하기 위해 emit 메서드를 사용합니다. 🎜

on, addListener

🎜 onaddListener 메서드를 통해 이벤트에 대한 리스너를 추가할 수 있습니다. 둘 다 사용법은 동일합니다🎜
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");
로그인 후 복사
로그인 후 복사
🎜첫 번째 매개변수는 이벤트 이름이고 두 번째 매개변수는 해당 콜백 함수입니다. 이 함수는 EventEmitter 인스턴스 객체가 emit를 호출하여 해당 이벤트를 트리거할 때 호출됩니다. 🎜
data 1
data 2
로그인 후 복사
로그인 후 복사
🎜와 같은 콜백 함수는 콘솔🎜
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' ]
로그인 후 복사
로그인 후 복사
🎜에서 data를 두 번 출력합니다. 위의 예에서 볼 수 있듯이 여러 콜백 함수를 동일한 이벤트에 바인딩할 수 있습니다. 🎜

실행 순서

🎜on 또는 addListener를 사용하여 여러 콜백 함수를 바인딩하는 경우 예를 들어 🎜
eventEmitter.removeAllListeners("error");
console.log(eventEmitter.eventNames()); // [ 'start', 'end' ]
로그인 후 복사
로그인 후 복사
🎜는 순서대로 🎜
const {EventEmitter} = require("events");
const eventEmitter = new EventEmitter();

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

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

});

console.log(eventEmitter.listenerCount("data")); // 2
로그인 후 복사
로그인 후 복사

반복적으로 추가

🎜되어 콘솔에 출력되며 를 사용합니다. on 메서드가 이벤트를 바인딩할 때 중복 제거 검사를 수행하지 않습니다. 🎜
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
로그인 후 복사
로그인 후 복사
🎜위 프로그램은 listener 함수를 이벤트에 두 번 바인딩합니다. 하지만 내부적으로는 그렇게 하지 않습니다. 중복 제거 전에 이 콜백 함수가 추가되었는지 확인하지 않으므로 리스너가 콘솔에 두 번 인쇄됩니다. 🎜

매개변수 전달

🎜 또한 콜백 함수는 emit을 통해 이벤트가 트리거될 때 매개변수를 전달받을 수도 있습니다. , 예: 🎜
const {EventEmitter} = require("events");
const eventEmitter = new EventEmitter();

eventEmitter.setMaxListeners(1);

console.log(eventEmitter.getMaxListeners()); // 1
로그인 후 복사
로그인 후 복사
🎜위의 내용 emit을 사용하여 이벤트를 트리거할 때 추가 매개변수가 전달되며 이는 콜백 함수에 전달됩니다. 🎜

동기 실행

🎜또 다른 문제는 이벤트가 동기적으로 발생하는지 비동기적으로 발생하는지입니다. 실험을 해보자🎜
const {EventEmitter} = require("events");

console.log(EventEmitter.defaultMaxListeners); // 10
로그인 후 복사
로그인 후 복사
🎜위의 이벤트를 발생시키고 있다는 정보가 출력됩니다. 이벤트가 비동기적으로 트리거되면 후속 print 문이 먼저 실행되고, 그렇지 않으면 이벤트에 바인딩된 콜백 함수가 먼저 실행됩니다. 실행 결과는 다음과 같습니다🎜
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");
로그인 후 복사
로그인 후 복사
🎜이벤트 트리거링이 동기적으로 실행되는 것을 볼 수 있습니다. 🎜

off, RemoveListener

🎜offremoveListener 메서드 on</code와 동일한 효과를 가집니다. > <code>addLsitener의 기능은 반대입니다. 특정 이벤트에 해당하는 콜백 함수를 삭제하는 기능입니다🎜
once
once
로그인 후 복사
로그인 후 복사
🎜콘솔 출력 결과는 🎜
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");
로그인 후 복사
로그인 후 복사
🎜이벤트가 처음 발생하면 둘 다입니다. 이벤트가 트리거되면 해당 이벤트에 대한 콜백 함수인 Listener1을 삭제했습니다. 따라서 두 번째로 트리거되면 Listener2만 트리거됩니다. 🎜
🎜참고: on 또는 addListener를 사용하여 익명 함수를 바인딩하는 경우 offremoveListener를 전달할 수 없습니다. 콜백 함수 바인딩을 해제합니다. 두 함수의 참조가 동일한지 비교하여 함수 바인딩을 해제하기 때문입니다. 🎜

once

🎜한 번만 실행되는 콜백 함수를 바인딩하려면 once를 사용하세요. callback 함수는 자동으로 바인딩 해제됩니다🎜
once
로그인 후 복사
로그인 후 복사
🎜위 코드에서는 한 번을 사용하여 콜백 함수를 data 이벤트에 바인딩한 다음 emit를 사용합니다. 메서드 두 번 트리거됩니다. once를 사용하여 바인딩된 콜백 함수는 한 번만 트리거되므로 두 번째로 트리거되면 콜백 함수가 실행되지 않으므로 데이터만 인쇄됩니다. 콘솔에서 한 번. 🎜🎜또한 on에 바인딩된 콜백 함수와 마찬가지로 emit 메서드를 통해 콜백 함수에 매개변수를 전달할 수도 있습니다🎜
class EventEmitter {
    constructor() {
        this.listeners = new Map();
    }
}
로그인 후 복사
로그인 후 복사
🎜콘솔에서 결과가 인쇄됩니다🎜
on(event, callback) {
    if(!this.listeners.has(event)) {
        this.listeners.set(event, []);
    }
    let fns = this.listeners.get(event);
    fns.push(callback);
}
로그인 후 복사
로그인 후 복사

prependListener, prependOnceListener

🎜on 또는 addListener를 사용하여 콜백 함수를 이벤트에 바인딩하면 실행됩니다. 추가 순서에 따라 prependLsitener를 사용하여 바인딩된 이벤트 콜백 함수가 다른 콜백 함수보다 먼저 실행됩니다 🎜
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);
    }
}
로그인 후 복사
로그인 후 복사
🎜prependOnceListenerprependListener와 동일하지만 이에 바인딩된 콜백 함수는 한 번만 실행됩니다. 🎜
emit(event, ...args) {
    if(!this.listeners.has(event)) {
        return;
    }
    for (let fn of fns) {
        fn(...args);
    }
}
로그인 후 복사
로그인 후 복사
🎜 위에서는 prependOnceListener를 사용하여 콜백 함수를 바인딩했습니다. 이벤트가 발생하면 콜백 함수가 이전에 실행된 다른 함수가 한 번만 실행되므로 두 번째로 함수를 트리거하면 콜백 함수가 실행되지 않고 콘솔 인쇄 결과는 다음과 같습니다. 🎜
prepend once
on
on
로그인 후 복사
로그인 후 복사

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");
로그인 후 복사
로그인 후 복사

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

data 1
data 2
로그인 후 복사
로그인 후 복사

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; ]
로그인 후 복사
로그인 후 복사

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

eventEmitter.removeAllListeners("error");
console.log(eventEmitter.eventNames()); // [ &#39;start&#39;, &#39;end&#39; ]
로그인 후 복사
로그인 후 복사

listenerCount

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

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

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

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

});

console.log(eventEmitter.listenerCount("data")); // 2
로그인 후 복사
로그인 후 복사

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");
});
로그인 후 복사
로그인 후 복사

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

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 个回调函数,可以通过 EventEmitterdefaultMaxListeners 属性获得该值

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

console.log(EventEmitter.defaultMaxListeners); // 10
로그인 후 복사
로그인 후 복사

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");
로그인 후 복사
로그인 후 복사

控制台打印结果为

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

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

class EventEmitter {
    constructor() {
        this.listeners = new Map();
    }
}
로그인 후 복사
로그인 후 복사

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);
}
로그인 후 복사
로그인 후 복사

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

addListener(event, callback) {
    this.on(event, callback);
}
로그인 후 복사
로그인 후 복사

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);
    }
}
로그인 후 복사
로그인 후 복사

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

emit(event, ...args) {
    if(!this.listeners.has(event)) {
        return;
    }
    for (let fn of fns) {
        fn(...args);
    }
}
로그인 후 복사
로그인 후 복사

一开始我也是这么写的,但是因为 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);
}
로그인 후 복사

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);
    }
}
로그인 후 복사

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

removeListener(event, callback) {
    this.off(event, callback);
}
로그인 후 복사

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);
}
로그인 후 복사

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

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

removeAllListeners(event) {
    // 如果没有传入 event,则删除所有事件
    if (event === undefined) {
        this.listeners = new Map();
        return;
    }
    this.listeners.delete(event);
}
로그인 후 복사

eventNames

获得已经绑定了哪些事件

eventNames() {
    return [...this.listeners.keys()];
}
로그인 후 복사

listenerCount

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

listenerCount(event) {
    return this.listeners.get(event).length;
}
로그인 후 복사

上述的实现有一个 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);
    }
}
로그인 후 복사

全部代码如下

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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:juejin.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!