中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Node.js中的EventEmitter模塊怎么使用

發布時間:2021-12-22 09:35:04 來源:億速云 閱讀:152 作者:iii 欄目:web開發

本篇內容主要講解“Node.js中的EventEmitter模塊怎么使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Node.js中的EventEmitter模塊怎么使用”吧!

Node.js中的EventEmitter模塊怎么使用

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

我們可以通過 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

removeAllListeners

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

通過 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

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.js中的EventEmitter模塊怎么使用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

西峡县| 宣城市| 格尔木市| 苍南县| 永嘉县| 板桥市| 皮山县| 宁南县| 海宁市| 五峰| 临桂县| 大足县| 江北区| 崇仁县| 南投市| 兖州市| 盘锦市| 永兴县| 南岸区| 黄石市| 永川市| 泰和县| 安乡县| 临澧县| 诸城市| 社旗县| 沛县| 乌拉特中旗| 应用必备| 滕州市| 彰武县| 白水县| 施甸县| 郁南县| 淅川县| 满城县| 雅安市| 舞阳县| 冕宁县| 华蓥市| 静乐县|