摘要:之前的文章里有說,在中,流是許許多多原生對象的父類,角色可謂十分重要。效率更高的從數(shù)組中去除一個元素。不過這個所提供的功能過于多了,它支持去除自定義數(shù)量的元素,還支持向數(shù)組中添加自定義的元素。
之前的文章里有說,在 Node.js 中,流(stream)是許許多多原生對象的父類,角色可謂十分重要。但是,當(dāng)我們沿著“族譜”往上看時,會發(fā)現(xiàn) EventEmitter 類是流(stream)類的父類,所以可以說,EventEmitter 類是 Node.js 的根基類之一,地位可顯一般。雖然 EventEmitter 類暴露的接口并不多而且十分簡單,并且是少數(shù)純 JavaScript 實現(xiàn)的模塊之一,但因為它的應(yīng)用實在是太廣泛,身份太基礎(chǔ),所以在它的實現(xiàn)里處處閃光著一些優(yōu)化代碼執(zhí)行效率,和保證極端情況下代碼結(jié)果正確性的小細(xì)節(jié)。在了解之后,我們也可以將其使用到我們的日常編碼之后,學(xué)以致用。
好,現(xiàn)在就讓我們跟隨 Node.js 項目中的 lib/events.js 中的代碼,來逐一了解:
效率更高的 鍵 / 值 對存儲對象的創(chuàng)建。
效率更高的從數(shù)組中去除一個元素。
效率更高的不定參數(shù)的函數(shù)調(diào)用。
如果防止在一個事件監(jiān)聽器中監(jiān)聽同一個事件,接而導(dǎo)致死循環(huán)?
emitter.once 是怎么辦到的?
效率更高的 鍵 / 值 對存儲對象的創(chuàng)建在 EventEmitter 類中,以 鍵 / 值 對的方式來存儲事件名和對應(yīng)的監(jiān)聽器。在 Node.js里 ,最簡單的 鍵 / 值 對的存儲方式就是直接創(chuàng)建一個空對象:
let store = {} store.key = "value"
你可能會說,ES2015 中的 Map 已經(jīng)在目前版本的 Node.js 中可用了,在語義上它更有優(yōu)勢:
let store = new Map() store.set("key", "value")
不過,你可能只需要一個純粹的 鍵 / 值 對存儲對象,并不需要 Object 和 Map 這兩個類的原型中的提供的那些多余的方法,所以你直接:
let store = Object.create(null) store.key = "value"
好,我們已經(jīng)做的挺極致了,但這還不是 EventEmitter 中的最終實現(xiàn),它的辦法是使用一個空的構(gòu)造函數(shù),并且把這個構(gòu)造的原型事先置空:
function Store () {} Store.prototype = Object.create(null)
然后:
let store = new Store() store.key = "value"
現(xiàn)在讓我們來比一比效率,代碼:
/* global suite bench */ "use strict" suite("key / value store", function () { function Store () {} Store.prototype = Object.create(null) bench("let store = {}", function () { let store = {} store.key = "value" }) bench("let store = new Map()", function () { let store = new Map() store.set("key", "value") }) bench("let store = Object.create(null)", function () { let store = Object.create(null) store.key = "value" }) bench("EventEmitter way", function () { let store = new Store() store.key = "value" }) })
比較結(jié)果:
key / value store 83,196,978 op/s ? let store = {} 4,826,143 op/s ? let store = new Map() 7,405,904 op/s ? let store = Object.create(null) 165,608,103 op/s ? EventEmitter way效率更高的從數(shù)組中去除一個元素
在 EventEmitter#removeListener 這個 API 的實現(xiàn)里,需要從存儲的監(jiān)聽器數(shù)組中除去一個元素,我們首先想到的就是使用 Array#splice 這個 API ,即 arr.splice(i, 1) 。不過這個 API 所提供的功能過于多了,它支持去除自定義數(shù)量的元素,還支持向數(shù)組中添加自定義的元素。所以,源碼中選擇自己實現(xiàn)一個最小可用的:
// lib/events.js // ... function spliceOne(list, index) { for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) list[i] = list[k]; list.pop(); }
比一比,代碼:
/* global suite bench */ "use strict" suite("Remove one element from an array", function () { function spliceOne (list, index) { for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) { list[i] = list[k] } list.pop() } bench("Array#splice", function () { let array = [1, 2, 3] array.splice(1, 1) }) bench("EventEmitter way", function () { let array = [1, 2, 3] spliceOne(array, 1) }) })
結(jié)果,好吧,秒了:
Remove one element from an array 4,262,168 op/s ? Array#splice 54,829,749 op/s ? EventEmitter way效率更高的不定參數(shù)的函數(shù)調(diào)用
在事件觸發(fā)時,監(jiān)聽器擁有的參數(shù)數(shù)量是任意的,所以源碼中優(yōu)化了不定參數(shù)的函數(shù)調(diào)用。
不過好吧,這里使用的是笨辦法,即...把不定參數(shù)的函數(shù)調(diào)用轉(zhuǎn)變成固定參數(shù)的函數(shù)調(diào)用,且最多支持到三個參數(shù):
// lib/events.js // ... function emitNone(handler, isFn, self) { // ... } function emitOne(handler, isFn, self, arg1) { // ... } function emitTwo(handler, isFn, self, arg1, arg2) { // ... } function emitThree(handler, isFn, self, arg1, arg2, arg3) { // ... } function emitMany(handler, isFn, self, args) { // ... }
雖然結(jié)果不言而喻,我們還是比較下會差多少,以三個參數(shù)為例:
/* global suite bench */ "use strict" suite("calling function with any amount of arguments", function () { function nope () {} bench("Function#apply", function () { function callMany () { nope.apply(null, arguments) } callMany(1, 2, 3) }) bench("EventEmitter way", function () { function callThree (a, b, c) { nope.call(null, a, b, c) } callThree(1, 2, 3) }) })
結(jié)果顯示差了一倍:
calling function with any amount of arguments 11,354,996 op/s ? Function#apply 23,773,458 op/s ? EventEmitter way如果防止在一個事件監(jiān)聽器中監(jiān)聽同一個事件,接而導(dǎo)致死循環(huán)?
在注冊事件監(jiān)聽器時,你可否曾想到過這種情況:
"use strict" const EventEmitter = require("events") let myEventEmitter = new EventEmitter() myEventEmitter.on("wtf", function wtf () { myEventEmitter.on("wtf", wtf) }) myEventEmitter.emit("wtf")
運行上述代碼,是否會直接導(dǎo)致死循環(huán)?答案是不會,因為源碼中做了處理。
我們先看一下具體的代碼:
// lib/events.js // ... function emitMany(handler, isFn, self, args) { if (isFn) handler.apply(self, args); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].apply(self, args); } } // ... function arrayClone(arr, i) { var copy = new Array(i); while (i--) copy[i] = arr[i]; return copy; }
其中的 handler 便是具體的事件監(jiān)聽器數(shù)組,不難看出,源碼中的解決方案是,使用 arrayClone 方法,拷貝出另一個一模一樣的數(shù)組,來執(zhí)行它,這樣一來,當(dāng)我們在監(jiān)聽器內(nèi)監(jiān)聽同一個事件時,的確給原監(jiān)聽器數(shù)組添加了新的函數(shù),但并沒有影響到當(dāng)前這個被拷貝出來的副本數(shù)組。
emitter.once 是怎么辦到的這個很簡單,使用了閉包:
function _onceWrap(target, type, listener) { var fired = false; function g() { target.removeListener(type, g); if (!fired) { fired = true; listener.apply(target, arguments); } } g.listener = listener; return g; }
你可能會問,我既然已經(jīng)在 g 函數(shù)中的第一行中移除了當(dāng)前的監(jiān)聽器,為何還要使用 fired 這個 flag ?我個人覺得是因為,在 removeListener 這個同步方法中,會將這個 g 函數(shù)暴露出來給 removeListener 事件的監(jiān)聽器,所以該 flag 用來保證 once 注冊的函數(shù)只會被調(diào)用一次。
最后分析就到這里啦,在了解了這些做法之后,在今后我們寫一些有性能要求的底層工具庫等東西時,我們便可以用上它們啦。EventEmitter 類的源碼并不復(fù)雜,并且是純 JavaScript 實現(xiàn)的,所以也非常推薦大家閑時一讀。
參考:https://github.com/nodejs/node/blob/master/lib/events.js
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/79295.html
摘要:為指定事件注冊一個單次監(jiān)聽器,即監(jiān)聽器最多只會觸發(fā)一次,觸發(fā)后立刻解除該監(jiān)聽器。移除指定事件的某個監(jiān)聽器,監(jiān)聽器必須是該事件已經(jīng)注冊過的監(jiān)聽器。返回指定事件的監(jiān)聽器數(shù)組。如何創(chuàng)建空對象我們已經(jīng)了解到,是要來儲存監(jiān)聽事件監(jiān)聽器數(shù)組的。 毫無疑問,nodeJS改變了整個前端開發(fā)生態(tài)。本文通過分析nodeJS當(dāng)中events模塊源碼,由淺入深,動手實現(xiàn)了屬于自己的ES6事件觀察者系統(tǒng)。千萬不...
摘要:實際上,我在看代碼的過程中順手提交了這個,作者眼明手快,當(dāng)天就進行了修復(fù),現(xiàn)在最新的代碼里已經(jīng)不是這個樣子了而且狀態(tài)機標(biāo)識由字符串換成了數(shù)字常量,解析更準(zhǔn)確的同時執(zhí)行效率也會更高。 最近饒有興致的又把最新版?Vue.js?的源碼學(xué)習(xí)了一下,覺得真心不錯,個人覺得 Vue.js 的代碼非常之優(yōu)雅而且精辟,作者本身可能無 (bu) 意 (xie) 提及這些。那么,就讓我來吧:) 程序結(jié)構(gòu)梳...
摘要:實際上,我在看代碼的過程中順手提交了這個,作者眼明手快,當(dāng)天就進行了修復(fù),現(xiàn)在最新的代碼里已經(jīng)不是這個樣子了而且狀態(tài)機標(biāo)識由字符串換成了數(shù)字常量,解析更準(zhǔn)確的同時執(zhí)行效率也會更高。 最近饒有興致的又把最新版?Vue.js?的源碼學(xué)習(xí)了一下,覺得真心不錯,個人覺得 Vue.js 的代碼非常之優(yōu)雅而且精辟,作者本身可能無 (bu) 意 (xie) 提及這些。那么,就讓我來吧:) 程序結(jié)構(gòu)梳...
摘要:感謝大神的免費的計算機編程類中文書籍收錄并推薦地址,以后在倉庫里更新地址,聲音版全文狼叔如何正確的學(xué)習(xí)簡介現(xiàn)在,越來越多的科技公司和開發(fā)者開始使用開發(fā)各種應(yīng)用。 說明 2017-12-14 我發(fā)了一篇文章《沒用過Node.js,就別瞎逼逼》是因為有人在知乎上黑Node.js。那篇文章的反響還是相當(dāng)不錯的,甚至連著名的hax賀老都很認(rèn)同,下班時讀那篇文章,竟然坐車的還坐過站了。大家可以很...
閱讀 2879·2021-11-22 11:56
閱讀 3597·2021-11-15 11:39
閱讀 926·2021-09-24 09:48
閱讀 789·2021-08-17 10:14
閱讀 1368·2019-08-30 15:55
閱讀 2780·2019-08-30 15:55
閱讀 1343·2019-08-30 15:44
閱讀 2813·2019-08-30 10:59