成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

通過源碼解析 Node.js 中 events 模塊里的優(yōu)化小細(xì)節(jié)

cloud / 3500人閱讀

摘要:之前的文章里有說,在中,流是許許多多原生對象的父類,角色可謂十分重要。效率更高的從數(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")

不過,你可能只需要一個純粹的 鍵 / 值 對存儲對象,并不需要 ObjectMap 這兩個類的原型中的提供的那些多余的方法,所以你直接:

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

相關(guān)文章

  • 解析nodeJS模塊源碼 親手打造基于ES6的觀察者系統(tǒng)

    摘要:為指定事件注冊一個單次監(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)。千萬不...

    csRyan 評論0 收藏0
  • Vue.js 源碼學(xué)習(xí)筆記

    摘要:實際上,我在看代碼的過程中順手提交了這個,作者眼明手快,當(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)梳...

    darkbaby123 評論0 收藏0
  • Vue.js 源碼學(xué)習(xí)筆記

    摘要:實際上,我在看代碼的過程中順手提交了這個,作者眼明手快,當(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)梳...

    jsdt 評論0 收藏0
  • 【全文】狼叔:如何正確的學(xué)習(xí)Node.js

    摘要:感謝大神的免費的計算機編程類中文書籍收錄并推薦地址,以后在倉庫里更新地址,聲音版全文狼叔如何正確的學(xué)習(xí)簡介現(xiàn)在,越來越多的科技公司和開發(fā)者開始使用開發(fā)各種應(yīng)用。 說明 2017-12-14 我發(fā)了一篇文章《沒用過Node.js,就別瞎逼逼》是因為有人在知乎上黑Node.js。那篇文章的反響還是相當(dāng)不錯的,甚至連著名的hax賀老都很認(rèn)同,下班時讀那篇文章,竟然坐車的還坐過站了。大家可以很...

    Edison 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<