<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                &emsp;&emsp;Events 是 Node.js 中最重要的核心模塊之一,很多模塊都是依賴其創建的,例如[上一節分析的流](https://www.cnblogs.com/strick/p/16225418.html),文件、網絡等模塊。 &emsp;&emsp;比較知名的 Express、KOA 等框架在其內部也使用了 Events 模塊。 &emsp;&emsp;Events 模塊提供了[EventEmitter](https://nodejs.org/dist/latest-v18.x/docs/api/events.html)類,EventEmitter 也叫事件觸發器,是一種觀察者模式的實現。 &emsp;&emsp;觀察者模式是軟件設計模式的一種,在此模式中,一個目標對象(即被觀察者對象)管理所有依賴于它的觀察者對象。 &emsp;&emsp;當其自身狀態發生變化時,將以廣播的方式主動發送通知(在通知中可攜帶一些數據),這樣就能在兩者之間建立觸發機制,達到解耦地目的。 &emsp;&emsp;與瀏覽器中的事件處理器不同,在 Node.js 中沒有捕獲、冒泡、preventDefault() 等概念或方法。 &emsp;&emsp;本系列所有的示例源碼都已上傳至Github,[點擊此處](https://github.com/pwstrick/node)獲取。 ## 一、方法原理 &emsp;&emsp;在下面的示例中,加載 events 模塊,實例化 EventEmitter 類,賦值給 demo 變量,聲明 listener() 監聽函數。 &emsp;&emsp;然后調用 demo 的 on() 方法注冊 begin 事件,最后調用 emit() 觸發 begin 事件,在控制臺打印出“strick”。 ~~~ const EventEmitter = require('events'); const demo = new EventEmitter(); const listener = () => { // 監聽函數 console.log('strick'); }; // 注冊 demo.on('begin', listener); demo.emit('begin'); ~~~ &emsp;&emsp;若要移除監聽函數,可以像下面這樣,注意,off() 方法不是移除事件,而是函數。 ~~~ demo.off('begin', listener); ~~~ **1)構造函數** &emsp;&emsp;在[src/lib/events.js](https://github.com/nodejs/node/blob/master/lib/events.js)文件中,可以看到構造函數的源碼,它會調用 init() 方法,并指定 this,也就是當前實例。 ~~~ function EventEmitter(opts) { EventEmitter.init.call(this, opts); } ~~~ &emsp;&emsp;刪減了 init() 方法源碼,只列出了關鍵部分,當 \_events 私有屬性不存在時,就通過 ObjectCreate(null) 創建。 &emsp;&emsp;之所以使用 ObjectCreate(null) 是為了得到一個不繼承任何原型方法的干凈鍵值對。\_events 的 key 是事件名稱,value 是監聽函數。 ~~~ EventEmitter.init = function(opts) { // 當 _events 私有屬性不存在時 if (this._events === undefined || this._events === ObjectGetPrototypeOf(this)._events) { this._events = ObjectCreate(null); // 不繼承任何原型方法的干凈鍵值對 this._eventsCount = 0; } }; ~~~ **2)on()** &emsp;&emsp;on() 其實是 addListener() 的別名,具體邏輯在 \_addListener() 函數中。 ~~~ EventEmitter.prototype.addListener = function addListener(type, listener) { return _addListener(this, type, listener, false); }; EventEmitter.prototype.on = EventEmitter.prototype.addListener; ~~~ &emsp;&emsp;在 \_addListener() 函數中,會對傳入的事件判斷之前是否注冊過。 &emsp;&emsp;如果之前未注冊過,那么就在鍵值對中注冊新的事件和監聽函數。 &emsp;&emsp;如果之前已注冊過,那么就將多個監聽函數合并成數組使用,在觸發時會依次執行。 &emsp;&emsp;EventEmitter 默認的事件最大監聽數是 10,若注冊的數量超出了這個限制,那么就會發出警告,不過事件仍然可以正常觸發。 ~~~ function _addListener(target, type, listener, prepend) { let m; let events; let existing; events = target._events; // 判斷傳入的事件是否注冊過 if (events === undefined) { events = target._events = ObjectCreate(null); target._eventsCount = 0; } else { existing = events[type]; } // 在鍵值對中注冊新的事件和監聽函數 if (existing === undefined) { events[type] = listener; ++target._eventsCount; } else { // 已存在相同名稱的事件 // 添加第二個相同名稱的事件時,將 events[type] 修改成數組 if (typeof existing === "function") { existing = events[type] = prepend ? [listener, existing] : [existing, listener]; } else if (prepend) { existing.unshift(listener); } else { // 若是數組,就添加到末尾 existing.push(listener); } // 讀取最大事件監聽數 m = _getMaxListeners(target); if (m > 0 && existing.length > m && !existing.warned) { existing.warned = true; const w = genericNodeError( `Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` + `added to ${inspect(target, { depth: -1 })}. Use emitter.setMaxListeners() to increase limit`, { name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length }); process.emitWarning(w); } } return target; } ~~~ &emsp;&emsp;在下面這個示例中,同一個事件,注冊了兩個監聽函數,在觸發時,會先打印“strick”,再打印“freedom”。 ~~~ const EventEmitter = require('events'); const demo = new EventEmitter(); const listener1 = () => { // 監聽函數 console.log('strick'); }; const listener2 = () => { // 監聽函數 console.log('freedom'); }; // 注冊 demo.on('begin', listener1); demo.on('begin', listener2); demo.emit('begin'); ~~~ &emsp;&emsp;EventEmitter 還提供了一個 once() 方法,也是用于注冊事件,但只會觸發一次。 **3)off()** &emsp;&emsp;off() 方法是 removeListener() 的別名。 ~~~ EventEmitter.prototype.off = EventEmitter.prototype.removeListener; ~~~ &emsp;&emsp;下面是刪減過的 removeListener() 方法源碼,先是讀取指定事件的監聽函數賦值給 list 變量,類型是函數或數組。 &emsp;&emsp;如果要移除的事件與 list 匹配,當只剩下一個事件時,就賦值 ObjectCreate(null);否則使用 delete 關鍵字刪除鍵值對的屬性。 &emsp;&emsp;如果 list 是一個數組時,就遍歷它,并記錄匹配位置。若匹配位置在頭部,就調用 shift() 方法移除,否則使用 splice() 方法。 ~~~ EventEmitter.prototype.removeListener = function removeListener(type, listener) { const events = this._events; // 讀取指定事件的監聽函數,類型是函數或數組 const list = events[type]; // 要移除的事件與 list 匹配 if (list === listener || list.listener === listener) { // 只剩下最后一個事件,就賦值 ObjectCreate(null) if (--this._eventsCount === 0) this._events = ObjectCreate(null); else { delete events[type]; // 刪除鍵值對的屬性 } } else if (typeof list !== "function") { let position = -1; // 遍歷 list 數組,若查到匹配的就記錄位置 for (let i = list.length - 1; i >= 0; i--) { if (list[i] === listener || list[i].listener === listener) { position = i; break; } } // 在頭部就直接調用 shift() 方法 if (position === 0) list.shift(); else { if (spliceOne === undefined) spliceOne = require("internal/util").spliceOne; // 沒有使用 splice() 方法,選擇了一個最小可用的函數 spliceOne(list, position); } } return this; }; ~~~ &emsp;&emsp;Node.js 沒有使用 splice() 方法,而是選擇了一個最小可用的函數,據說性能有所提升。 &emsp;&emsp;spliceOne() 函數很簡單,如下所示,從指定索引加一的位置開始循環,后一個元素向前搬移到上一個元素的位置,再將最后那個元素移除。 ~~~ function spliceOne(list, index) { for (; index + 1 < list.length; index++) list[index] = list[index + 1]; list.pop(); } ~~~ **4)emit()** &emsp;&emsp;下面是刪減過的 emit() 方法源碼,首先讀取監聽函數并賦值給 handler。 &emsp;&emsp;若 handler 是函數,則直接通過 apply() 運行。 &emsp;&emsp;若 handler 是數組,那么先調用 arrayClone() 函數將其克隆,在遍歷數組,依次通過 apply() 運行。 ~~~ EventEmitter.prototype.emit = function emit(type, ...args) { const handler = events[type]; // 若 handler 是函數,則直接運行 if (typeof handler === 'function') { handler.apply(this, args); } else { const len = handler.length; // 數組克隆,防止在 emit 時移除事件對其進行干擾 const listeners = arrayClone(handler); // 遍歷數組 for (let i = 0; i < len; ++i) { listeners[i].apply(this, args); } } return true; }; ~~~ &emsp;&emsp;arrayClone() 函數的作用是防止在 emit 時移除事件對其進行干擾,在函數中使用 switch 分支和數組的 slice() 方法。 &emsp;&emsp;官方說從 Node 版本 8.8.3 開始,這個實現要比簡單地 for 循環快。 ~~~ function arrayClone(arr) { // 從 V8.8.3 開始,這個實現要比簡單地 for 循環快 switch (arr.length) { case 2: return [arr[0], arr[1]]; case 3: return [arr[0], arr[1], arr[2]]; case 4: return [arr[0], arr[1], arr[2], arr[3]]; case 5: return [arr[0], arr[1], arr[2], arr[3], arr[4]]; case 6: return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]]; } // array.prototype.slice return ArrayPrototypeSlice(arr); } ~~~ ## 二、其他概念 **1)同步** &emsp;&emsp;官方明確指出 EventEmitter 是按照注冊的順序同步地調用所有監聽函數,避免競爭條件和邏輯錯誤。 &emsp;&emsp;在適當的時候,監聽函數可以使用 setImmediate() 或 process.nextTick() 方法切換到異步的操作模式,如下所示。 ~~~ const EventEmitter = require('events'); const demo = new EventEmitter(); demo.on('async', (a, b) => { setImmediate(() => { console.log(a, b); }); }); demo.emit('async', 'a', 'b'); ~~~ **2)循環** &emsp;&emsp;先來看第一個循環的示例,在注冊的 loop 事件中,會不斷地觸發 loop 事件,那么最終會報棧溢出的錯誤。 ~~~ const EventEmitter = require('events'); const demo = new EventEmitter(); const listener = () => { console.log('strick'); }; demo.on('loop', () => { demo.emit('loop'); listener(); }); demo.emit('loop'); // 報錯 ~~~ &emsp;&emsp;再看看第二個循環的示例,在注冊的 loop 事件中,又注冊了一次 loop 事件,這么處理并不會報錯,因為只是多注冊了一次同名事件而已。 ~~~ const listener = () => { console.log('strick'); }; demo.on('loop', () => { demo.on('loop', listener); listener(); }); demo.emit('loop'); // strick demo.emit('loop'); // strick strick ~~~ &emsp;&emsp;在每次觸發時,打印的數量要比上一次多一個。 **3)錯誤處理** &emsp;&emsp;在下面這個示例中,由于沒有注冊 error 事件,因此只要一觸發 error 事件就會拋出錯誤,后面的打印也不會執行。 ~~~ const EventEmitter = require('events'); const demo = new EventEmitter(); demo.emit('error', new Error('error')); console.log('strick'); ~~~ &emsp;&emsp;將代碼做下調整,為了防止 Node.js 主線程崩潰,應該始終注冊 error 事件,改造后,雖然也會報錯,但是打印仍然能正常執行。 ~~~ demo.on('error', err => { console.error(err); }); demo.emit('error', new Error('error')); console.log('strick'); ~~~ 參考資料: [Node.js技術棧之事件觸發器](https://www.nodejs.red/#/nodejs/events)?[異步迭代器](https://mp.weixin.qq.com/s/PDCZ5FreFJDJDqpvOe3xKQ)? [餓了么事件異步面試題](https://github.com/ElemeFE/node-interview/tree/master/sections/zh-cn#%E4%BA%8B%E4%BB%B6%E5%BC%82%E6%AD%A5) [深入理解Node.js之Event](https://yjhjstz.gitbooks.io/deep-into-node/content/chapter7/chapter7-1.html) [Node.js事件模塊](http://nodejs.cn/learn/the-nodejs-events-module)?[events事件模塊](https://nodejs.org/dist/latest-v18.x/docs/api/events.html) [EventEmitter 源碼分析與簡易實現](https://segmentfault.com/a/1190000016654243) [源碼分析:EventEmitter](https://juejin.cn/post/6969843023190425636) [詳解Object.create(null)](https://juejin.cn/post/6844903589815517192) ***** > 原文出處: [博客園-Node.js精進](https://www.cnblogs.com/strick/category/2154090.html) [知乎專欄-前端性能精進](https://www.zhihu.com/column/c_1611672656142725120) 已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。 ![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200) 推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看