1. 程式人生 > >node.js事件events詳解

node.js事件events詳解

nodejs基於事件驅動

事件釋出,事件訂閱

  • 大多數 Node.js 核心 API 構建於慣用的非同步事件驅動架構,其中某些型別的物件(又稱觸發器,Emitter)會觸發命名事件來呼叫函式(又稱監聽器,Listener)。同步函式,非同步觸發,通過回撥函式處理非同步。

例如,net.Server 會在每次有新連線時觸發事件,fs.ReadStream 會在檔案被開啟時觸發事件,stream會在資料可讀時觸發事件。

所有能觸發事件的物件都是 EventEmitter 類的例項。 這些物件有一個 eventEmitter.on() 函式,用於將一個或多個函式繫結到命名事件上。 事件的命名通常是駝峰式的字串。

當 EventEmitter 物件觸發一個事件時,所有繫結在該事件上的函式都會被同步地呼叫。

  • 這是node.js文件的一個例子: 我的程式碼下載index.js檔案裡,通過node index執行
const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('觸發事件');
});
myEmitter.emit('event'
); //這個就會觸發myEmitter.on裡的回撥函式
  • 實際上這個例子不太好,我再處理一下:

放在以前,我們處理"非同步"都是這樣做的(需要的話會傳一些引數啥的):

let fn = () => {
  console.log("資料結果的處理");
  
}
setTimeout(() => {
    console.log("得到資料");
    fn()
}, 2000);

在純的js裡,雖然可以達到預期目標,但是層層巢狀的函式實在是太讓人頭疼,為了減少這種麻煩,node.js的events的事件監聽就能很好的解決。

  • EventEmitter,釋出-訂閱模式:

const
EventEmitter = require("events").EventEmitter const myEmitter = new EventEmitter let fn = () => { console.log("資料結果的處理"); } //為了好看設定的定時********* setTimeout(() => { console.log("1s"); }, 1000); setTimeout(() => { console.log("1.5s"); }, 1500); setTimeout(() => { console.log("2s"); }, 2000); //************************ setTimeout(() => { console.log("2.5s 得到資料"); myEmitter.emit("dataMake") }, 2500); myEmitter.on("dataMake",fn)

在這裡插入圖片描述

這樣,非同步觸發回撥函式將會變得更加好歸類、結構化。

用於物件

  • 讓物件繼承事件的EventEmitter的原型:
const EventEmitter = require("events").EventEmitter

function FnObj(params) {
    this.name = params;
}

FnObj.prototype.__proto__=EventEmitter.prototype

const obj = new FnObj("Lucy");
obj.on("ly",function() {
    console.log(this.name);
    //注意箭頭函式沒有this哦
})
setTimeout(() => {
    obj.emit("ly")
}, 2000);

在這裡插入圖片描述

這就是node.js的事件,釋出-訂閱模式

events使用進階

非同步執行的處理函式

雖然對函式的處理變得更加結構有序,但是非同步執行總會發生一些資料競爭的情況,誰也不知道什麼時候資料能被真正的準備好供非同步函式使用。

或許有人會說我的處理函式在資料得到返回的函式裡顯示呼叫呢?然後現實開發中並不一定完全按照你的想法來執行,很多網站的資料並不能僅通過一個表單獲取,也不一定保證兩個資料的先後結果,在兩個資料集合得到時間不一致或不能保證一致的情況下,非同步執行就是個問題。

所以,處理好函式執行順序則是至關重要的:

  • 第一步,搞清楚到底傳了什麼引數進去

傳引數和this到你的監聽器:

eventEmitter.emit() 方法可以傳任意數量的引數到監聽器函式。 當監聽器函式被呼叫時,this 關鍵詞會被指向監聽器所繫結的 EventEmitter 例項

不要用箭頭函式 不要用箭頭函式 不要用箭頭函式 重要的事情說三遍,箭頭函式沒有this,它根本指不到你的例項

const EventEmitter = require("events").EventEmitter

const myEmitter = new EventEmitter();
myEmitter.on('event', function (a, b) {
    console.log(a, b);
    console.log(this);
    console.log(this === myEmitter);
});
myEmitter.emit('event', '引數a', '引數b');

在這裡插入圖片描述

貼心的我又簡單優化了node文件的列印結果,更加易讀

但是如果有些人就是倔強,我就要寫箭頭函式,我牛逼:

const EventEmitter = require("events").EventEmitter

const myEmitter = new EventEmitter();
myEmitter.on('event',  (a, b)=> {
    console.log(a, b);
    console.log(this);
    console.log(this === myEmitter);
});
myEmitter.emit('event', '引數a', '引數b');

對比上一次的結果: 在這裡插入圖片描述

const EventEmitter = require("events").EventEmitter

const myEmitter = new EventEmitter();
myEmitter.on('event',  (a, b,othis)=> {
    console.log(a, b);
    console.log(othis);
    console.log(othis === myEmitter);
});
myEmitter.emit('event', '引數a', '引數b', myEmitter);

非要用箭頭函式,但是這樣不很麻煩嗎? 在這裡插入圖片描述

同步還是非同步,無所謂

  • 加上引數傳遞,一切都不是障礙了:
const EventEmitter = require("events").EventEmitter

const myEmitter = new EventEmitter();
let data = {}

myEmitter.on('event', function (data) {
    setImmediate(() => {
    	//假如這裡有個數據結構化、圖形化的處理函式
        console.log('非同步進行結果處理');
        console.log(data.name, data.age, data.height,data.sex);
    });
});
myEmitter.emit('event', data); //雖然我立馬就準備觸發資料處理函式

process.nextTick(() => {
    console.log("同步的程式碼去獲取到了資料")
    data.name = "小芳";
    data.age = "18"
} )
process.nextTick(() => {
    console.log("同步的程式碼去獲取到了資料")
    data.height = "1米8"
})
Promise.resolve("女").then(res => {
    console.log("非同步的then函式也去拿了一下資料")
    data.sex = res
})

即便我先於獲取資料的函式呼叫了資料處理的函式,但是結合同步非同步函式,資料處理得到了最後的保障:

在這裡插入圖片描述

也有人可能會問,你這資料暴露在全域性這樣做有意義嗎?

回答:這裡是為了呼叫方便,你完全可以將資料請求封裝起來,讓他們在區域性執行,並將事件觸發也放在區域性作用域裡,引數傳遞只是事件釋出函式的基本功能,合理使用效果更佳!

監聽事件的行為

  • 監聽事件的建立:
const EventEmitter = require("events").EventEmitter
const myEmitter = new EventEmitter();

myEmitter.on('newListener',function() {
    console.log('有事件創建出來');
})
myEmitter.on('event', function () {
    console.log('事件處理函式呼叫');
});
// myEmitter.emit('event')
//列印:有事件創建出來

如果我重新開啟事件觸發,那麼結果是這樣 在這裡插入圖片描述

  • 事件解綁:
const EventEmitter = require("events").EventEmitter
const myEmitter = new EventEmitter();

function fn() {
    console.log('事件處理函式呼叫');
}
myEmitter.on('newListener',function() {
    console.log('有事件創建出來');
})
myEmitter.on('event', fn);
myEmitter.emit('event')
myEmitter.off('event', fn);
myEmitter.emit('event')
myEmitter.emit('event')
myEmitter.emit('event')

在這裡插入圖片描述

事件解綁是將回調函式與監聽的事件分離,如果想上面一樣直接把回撥函式寫在事件繫結函式裡是無法解綁,下面是錯誤的示範在這裡插入圖片描述

在這裡插入圖片描述

  • 監聽事件的解綁:
const EventEmitter = require("events").EventEmitter
const myEmitter = new EventEmitter();

function fn() {
    console.log('事件處理函式呼叫');
}
myEmitter.on('newListener',function() {
    console.log('有事件創建出來');
})
myEmitter.on('removeListener', function () {
    console.log('有事件被解綁');
})
myEmitter.on('event', fn);
myEmitter.emit('event')
myEmitter.off('event', fn);
myEmitter.emit('event')
myEmitter.emit('event')
myEmitter.emit('event')

在這裡插入圖片描述

  • 或者只讓事件執行一次:
const EventEmitter = require("events").EventEmitter
const myEmitter = new EventEmitter();

function fn() {
    console.log('事件處理函式呼叫');
}
myEmitter.on('newListener',function() {
    console.log('有事件被建立1');
})
myEmitter.on('newListener',function() {
    console.log('有事件被建立2');
})
myEmitter.on('removeListener', function () {
    console.log('有事件被解綁');
})
myEmitter.once('event', fn);
console.log('1111111111111111111111');
myEmitter.emit('event')
console.log('2222222222222222222222');
myEmitter.emit('event')
myEmitter.emit('event')
myEmitter.emit('event')

在這裡插入圖片描述

兩個事件監聽器弄清楚流程: 事件新增監聽器1被建立,但是當時沒有監聽事件建立的監聽器; 事件新增監聽器2被建立,觸發了事件建立監聽器1,列印:事件被建立1; 事件移除監聽器被建立,觸發事件建立監聽器1,2,列印:事件被建立1,事件被建立2; 事件’event’建立一次性事件,觸發事件建立監聽器1,2,列印:事件被建立1,事件被建立2; log列印:11111111111111111; emit觸發事件event,觸發解綁事件監聽器,處理事件佇列裡的event事件, 列印:有事件被解綁,事件處理函式被呼叫; log列印:22222222222222222; 由於事件event被解綁,無法被呼叫,後面沒有列印結果。。。

這裡需要注意的就是,nodejs裡的事件佇列的事件是在監聽建立觸發後再執行事件的觸發