【原創(chuàng)】Node核心API(二)Events

Events模塊是Node對“發(fā)布/訂閱”模式(publish/subscribe)的實現(xiàn),一個對象通過這個模塊蠕嫁,向另一個對象傳遞消息蚜点,幾乎所有常用的node模塊都繼承了events模塊,比如http拌阴、fs等绍绘。
Node中的Event模塊僅僅提供了一個對象: EventEmitter, EventEmitter 的核心就是事件觸發(fā)與事件監(jiān)聽器功能的封裝。

1迟赃、訂閱發(fā)布模式(Subscribe/Publish)

訂閱發(fā)布模式(又稱事件監(jiān)聽器模式)廣泛用于異步編程中陪拘。events模塊是訂閱發(fā)布模式的一個簡單實現(xiàn)。訂閱發(fā)布模式定義了一種一對多的依賴關(guān)系,在Node中EventEmitter 對象上開放了一個可以用于監(jiān)聽的on(eventName,callback)函數(shù),允許將一個或多個函數(shù)綁定到對應的事件上纤壁。當 EventEmitter 對象觸發(fā)一個事件時,所有綁定在該事件上的函數(shù)都被同步地調(diào)用左刽!

2、Events的API

Events API概覽

大多數(shù)時候我們不會直接使用 EventEmitter酌媒,而是在對象中繼承它欠痴。包括 fs、net秒咨、 http 在內(nèi)的喇辽,只要是支持事件響應的核心模塊都是 EventEmitter 的子類。
原因有兩點:

  • 具有某個實體功能的對象實現(xiàn)事件符合語義雨席, 事件的監(jiān)聽和發(fā)射應該是一個對象的方法菩咨。
  • JavaScript 的對象機制是基于原型的,支持 部分多重繼承,繼承 EventEmitter 不會打亂對象原有的繼承關(guān)系抽米。

3特占、方法

3.1 創(chuàng)建監(jiān)聽器

  • emitter.on(eventName, listener):添加 listener 函數(shù)到名為 eventName 的事件的監(jiān)聽器數(shù)組的末尾。 不會檢查 listener 是否已被添加云茸。 多次調(diào)用并傳入相同的 eventName 與 listener 會導致 listener 會被添加多次是目。
  • emitter.addListener(eventName, listener):emitter.on(eventName, listener)的別名。
//引入events模塊
const EventEmitter = require('events');
let count = 0;
//創(chuàng)建一個新實例
const myEmitter = new EventEmitter();
//給“去逛街”創(chuàng)建一個監(jiān)聽
myEmitter.on('去逛街', () => {
    console.log(`買了${++count}件衣服`);
});
// 再次給“去逛街”創(chuàng)建一個監(jiān)聽标捺,不會檢查 listener 是否已被添加胖笛,依然被放到監(jiān)聽器數(shù)組后面。
myEmitter.on('去逛街', () => {
    console.log(`買了${++count}條褲子`);
});
//觸發(fā)監(jiān)聽“去逛街”這個事件
myEmitter.emit('去逛街');
myEmitter.emit('去逛街');
/** 輸出結(jié)果:
 * 買了1件衣服
 * 買了2條褲子
 * 買了3件衣服
 * 買了4條褲子*/
  • emitter.once(eventName, listener):添加單次監(jiān)聽器 listener 到名為 eventName 的事件宜岛。 當 eventName 事件下次觸發(fā)時,監(jiān)聽器會先被移除功舀,然后再調(diào)用萍倡。
//引入events模塊
const EventEmitter = require('events');
let count = 0;

//創(chuàng)建一個新實例
const myEmitter = new EventEmitter();
//給“去逛街”創(chuàng)建一個監(jiān)聽
myEmitter.once('去逛街', () => {
    console.log(`買了${++count}件衣服`);
});
//觸發(fā)監(jiān)聽“去逛街”這個事件
myEmitter.emit('去逛街'); // 買了1件衣服

myEmitter.emit('去逛街'); // 沒有被觸發(fā)
  • emitter.prependListener(eventName, listener):添加 listener 函數(shù)到名為 eventName 的事件的監(jiān)聽器數(shù)組的開頭。 不會檢查 listener 是否已被添加辟汰。 多次調(diào)用并傳入相同的 eventName 和 listener 會導致 listener 被添加多次列敲。
  • emitter.prependOnceListener(eventName, listener):添加單次監(jiān)聽器 listener 到名為 eventName 的事件的監(jiān)聽器數(shù)組的開頭。 當 eventName 事件下次觸發(fā)時帖汞,監(jiān)聽器會先被移除戴而,然后再調(diào)用。
//引入events模塊
const EventEmitter = require('events');
//創(chuàng)建一個新實例
const myEmitter = new EventEmitter();
//給“去逛街”創(chuàng)建一個監(jiān)聽
myEmitter.on('去逛街', () => {
    console.log(`買衣服`);
});
// 添加 listener 函數(shù)到名為 eventName 的事件的監(jiān)聽器數(shù)組的開頭翩蘸。
myEmitter.prependListener('去逛街', () => {
    console.log(`買褲子`);
});
// 添加單次監(jiān)聽器 listener 到名為 eventName 的事件的監(jiān)聽器數(shù)組的開頭
myEmitter.prependOnceListener('去逛街', () => {
    console.log(`買鞋子`);
});
//觸發(fā)監(jiān)聽“去逛街”這個事件
myEmitter.emit('去逛街'); // 輸出結(jié)果:買鞋子 買褲子 買衣服
myEmitter.emit('去逛街'); // 輸出結(jié)果:買褲子 買衣服

3.2 調(diào)用監(jiān)聽器

  • emitter.emit(eventName[, ...args]):按照監(jiān)聽器注冊的順序所意,同步地調(diào)用每個注冊到名為 eventName 的事件的監(jiān)聽器,并傳入提供的參數(shù)催首。

3.3 移除監(jiān)聽器

  • emitter.removeListener(eventName, listener):從名為 eventName 的事件的監(jiān)聽器數(shù)組中移除指定的 listener扶踊。每次只會從監(jiān)聽器數(shù)組中移除一個監(jiān)聽器。 如果監(jiān)聽器被多次添加到指定 eventName 的監(jiān)聽器數(shù)組中郎任,則必須多次調(diào)用 removeListener() 才能移除所有實例秧耗。
  • emitter.removeAllListeners([eventName]):移除全部監(jiān)聽器或指定的 eventName 事件的監(jiān)聽器。
  • emitter.off(eventName, listener)emitter.removeListener() 的別名。
//引入events模塊
const EventEmitter = require('events');
//創(chuàng)建一個新實例
const myEmitter = new EventEmitter();
const buyClothes = () => {
    console.log(`買衣服`);
    myEmitter.removeListener('去逛街', buyPants);
}
const buyPants = () => {
    console.log(`買褲子`);
}
myEmitter.on('去逛街', buyClothes);
myEmitter.on('去逛街', buyPants);
// buyClothes 移除了監(jiān)聽器 buyPants,但它依然會被調(diào)用偿枕。
// 觸發(fā)時內(nèi)部的監(jiān)聽器數(shù)組為 [buyClothes, buyPants]
myEmitter.emit('去逛街'); // 買衣服 買褲子
// buyPants 現(xiàn)已被移除年局。
// 內(nèi)部的監(jiān)聽器數(shù)組為 [buyClothes]
myEmitter.emit('去逛街'); // 買衣服

4、事件

  • 'newListener' 事件: EventEmitter 實例在新的監(jiān)聽器被添加到其內(nèi)部監(jiān)聽器數(shù)組之前席楚,會觸發(fā)自身的 'newListener' 事件。在 'newListener' 回調(diào)中注冊到相同 eventName的任何其他監(jiān)聽器將插入到正在添加的監(jiān)聽器之前。
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
// EventEmitter 實例會在一個監(jiān)聽器被添加到其內(nèi)部監(jiān)聽器數(shù)組之前觸發(fā)自身的 'newListener' 事件缩麸;
// 只處理一次,避免無限循環(huán)。
myEmitter.once('newListener', (event, listener) => {
    if (event === '去逛街') {
        console.log('逛街前的準備');
        myEmitter.on('去逛街', () => {
            console.log('涂防曬霜');
        });
    }
});
myEmitter.on('去逛街', () => {
    console.log('買護膚品');
});
myEmitter.emit('去逛街'); // 逛街前的準備 涂防曬霜 買護膚品
myEmitter.emit('去逛街'); // 涂防曬霜 買護膚品

注意:對'newListener' 事件的監(jiān)聽要放在普通監(jiān)聽前面杭朱。如下'newListener' 事件不起作用阅仔。

const EventEmitter = require('events');
const myEmitter = new EventEmitter();
myEmitter.on('去逛街', () => {
    console.log('買護膚品');
});
myEmitter.once('newListener', (event, listener) => {
    if (event === '去逛街') {
        console.log('逛街前的準備');
        myEmitter.on('去逛街', () => {
            console.log('涂防曬霜');
        });
    }
});
myEmitter.emit('去逛街'); // 買護膚品
myEmitter.emit('去逛街'); // 買護膚品
  • 'removeListener' 事件:'removeListener' 事件在 listener 被移除后觸發(fā)
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
const buySkinProd = () => {
    console.log('買護膚品');
    myEmitter.removeListener('去逛街', buySkinProd);
}
myEmitter.once('removeListener', (event, listener) => {
    if (event === '去逛街') {
        console.log('逛街結(jié)束');
    }
});
myEmitter.on('去逛街', buySkinProd);
myEmitter.emit('去逛街'); // 買護膚品 逛街結(jié)束

5、自定義的類繼承 events

// blog.js
const EventEmitter=require('events');

class Base extends EventEmitter {
    constructor() {
        super();
    }
    onEvent(eventName,callback){
        super.on(eventName,callback);
    }
    emitEvent(eventName,arg){
        super.emit(eventName,arg);
    }
};

class BlogInfo extends Base {
    constructor() {
        super();
    }
    onSave() {
        super.onEvent('saveStart',function(blog){
            console.log('saveStart',blog);
        });

        super.onEvent('blogCount',function(blog){
            console.log('blogCount',blog.length);
        });

        super.onEvent('saveEnd',function(blog){
            console.log('saveEnd',blog);
        });
    }
    emitEvent(blog) {
        super.emitEvent('saveStart',blog);

        super.emitEvent('blogCount',blog);

        super.emitEvent('saveEnd',blog);
    }
}

exports.blogSave=function(newblog){
    console.log(BlogInfo.__proto__.__proto__ === EventEmitter); // true
    console.log(BlogInfo.__proto__ === Base); // true
    const blogInfo=new BlogInfo();
    blogInfo.onSave(newblog);
    blogInfo.emitEvent(newblog);
};
// index.js
const http = require('http');
const blog = require('./blog');
const serve = http.createServer((req, res) => {
    if (req.url === '/') {
        const newblog = {title: "標題", content: "內(nèi)容"};
        blog.blogSave(newblog);
        res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'});
        res.write('<html><body>');
        res.write('<h2>Hello World!</h2>');
        res.end('</body></html>');
    }
});
serve.listen(8000);
console.log('listen 8000');
/** 運行index.js結(jié)果
 * listen 8000
 * true
 * true
 * saveStart [ { title: '標題', content: '內(nèi)容' } ]
 * saveEnd [ { title: '標題', content: '內(nèi)容' } ]
 */

參考文章:
https://www.jb51.net/article/124799.htm
http://www.reibang.com/p/fd1f8c998a2c
http://www.reibang.com/p/152fddf0628c

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弧械,一起剝皮案震驚了整個濱河市八酒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌刃唐,老刑警劉巖羞迷,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異画饥,居然都是意外死亡衔瓮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門抖甘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來热鞍,“玉大人,你說我怎么就攤上這事衔彻∞背瑁” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵艰额,是天一觀的道長澄港。 經(jīng)常有香客問我,道長柄沮,這世上最難降的妖魔是什么回梧? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮祖搓,結(jié)果婚禮上漂辐,老公的妹妹穿的比我還像新娘。我一直安慰自己棕硫,他們只是感情好髓涯,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哈扮,像睡著了一般纬纪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滑肉,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天包各,我揣著相機與錄音,去河邊找鬼靶庙。 笑死问畅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播护姆,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼矾端,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了卵皂?” 一聲冷哼從身側(cè)響起秩铆,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎灯变,沒想到半個月后殴玛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡添祸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年滚粟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刃泌。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡凡壤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蔬咬,到底是詐尸還是另有隱情,我是刑警寧澤沐寺,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布林艘,位于F島的核電站,受9級特大地震影響混坞,放射性物質(zhì)發(fā)生泄漏狐援。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一究孕、第九天 我趴在偏房一處隱蔽的房頂上張望啥酱。 院中可真熱鬧,春花似錦厨诸、人聲如沸镶殷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绘趋。三九已至,卻和暖如春颗管,著一層夾襖步出監(jiān)牢的瞬間陷遮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工垦江, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留帽馋,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像绽族,于是被迫代替她去往敵國和親姨涡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容

  • ??events模塊是node的核心模塊项秉,幾乎所有常用的node模塊都繼承了events模塊绣溜,比如http、fs等...
    小小的開發(fā)人員閱讀 479評論 0 1
  • ??在 js 中事件驅(qū)動的代碼隨處可見娄蔼,這篇文章就介紹一下 node 中的 Events 模塊怖喻,以及理解如何實現(xiàn)。...
    涯丨角閱讀 453評論 0 1
  • 前言: 因為以前學習Node.js并沒有真正意義上的去學習它岁诉,而是粗略的學習了npm的常用命令和Node.js一些...
    Srtian閱讀 986評論 1 17
  • Node.js EventEmitter Node.js 所有的異步 I/O 操作在完成時都會發(fā)送一個事件到事件隊...
    FTOLsXD閱讀 315評論 1 2
  • https://nodejs.org/api/documentation.html 工具模塊 Assert 測試 ...
    KeKeMars閱讀 6,330評論 0 6