[譯]你不知道的NodeJS

你不知道的NodeJS

更新:這篇文章如今是個人書《Node.js進階》的一部分。 在jscomplete.com/node-beyond…中閱讀此內容的更新版本以及有關Node.js的更多信息。node

在今年的Forward.js會議(關於JavaScript的會議)上,我分享了題爲「你不知道的NodeJS」的演講。 在那次演講中,我向觀衆提出了一系列有關Nodejs運行時的問題,大多數有技術背景的觀衆沒法回答其中大多數問題。git

我沒有真正去統計這個數據但確實能在會議室裏感受到。演講後一些有勇氣的人走近我而且認可了這個事實。github

這就是讓我發表演講的緣由。 我認爲咱們沒有以正確的方式教授Node.js! 關於Node.js的大多數學習內容都聚焦於Node包上,而不是它的運行時上。 大多數Node包將模塊封裝在自身的Node運行時中(例如http或stream)。 當你遇到問題時,這些問題多是在自身運行時發生,而且若是你不瞭解Node運行時,就會遇到麻煩。瀏覽器

關於Node.js的大多數學習內容都聚焦於Node包上,而不是它的運行時上。

我爲這篇文章精選一些問題和回答。列以下的標題中,能夠嘗試先鬧中回答它們。(若是你在這裏發現了錯誤或者有歧義的回答,請讓我知道)bash

問題 #1: 什麼是調用堆棧?它是V8的一部分嗎?

調用確定是V8的一部分。它是V8用於保存函數調用軌跡的一種數據結構。每次咱們運行一個函數,V8都會將該函數的引用放入調用堆棧,並對該函數中嵌套的其餘函數進行相同的操做。這也包括遞歸調用的函數。服務器

當嵌套的函數運行結束,V8將一次彈出一個函數並用它的返回值替換它的位置。

爲何這對於Node很重要? 由於每一個Node進程僅得到一個調用堆棧。 若是使該調用堆棧處於繁忙狀態,則整個Node進程都處於繁忙狀態。記住這一點。數據結構

問題 #2: 什麼是事件輪詢? 它是V8的一部分嗎?

你認爲下圖中的事件輪詢在哪裏?架構

事件輪詢由 libuv模塊提供,它不是V8的一部分。

事件輪詢是處理外部事件並將它們轉換回調函數運行的一種機制。這種輪詢會循環的從事件隊列中選擇事件執行,並將它們的回調函數推入調用堆棧中。異步

若是這是你第一次聽到事件循環,則這些定義不會有太大幫助。 事件循環只是更大架構下中的一部分:函數

你須要理解事件輪詢背後更大的架構、V8所扮演的角色、 Node.js的APIs以及知道這些事情是如何推入隊列並被V8執行的。

Node.jsAPIs是像setTimeoutfs.readFile這樣的函數。這些並非JavaScript的一部分,而由Node.js提供的函數。

事件循環位於這張照片的中間(其實是它的一個更復雜的版本),而且像一個組織者。 當V8調用堆棧爲空時,事件循環能夠決定下一步執行什麼。

問題 #3: 當調用堆棧和事件輪詢隊列所有爲空時,Node.js會作什麼?

它簡單的退出.

當你啓動一個Node.js進程時,Node將自動啓動事件輪詢。當事件輪詢處於空閒狀態而且無其餘事件去處理時,程序將退出。

To keep a Node process running, you need to place something somewhere in event queues. For example, when you start a timer or an HTTP server you are basically telling the event loop to keep running and checking on these events. 爲了保持Node進程運行,你須要向事件隊列中放入一些內容。好比,當你能夠啓動一個定時器或者一個 HTTP服務時,你就至關於告訴事件輪詢保持運行,同時去監聽一些事件。

問題 #4: 除了V8和Libuv,Node還具備其餘哪些外部依賴項?

如下是一個Node進程全部可使用的獨立庫:

  • http-parser
  • c-ares
  • OpenSSL
  • zlib 它們全部都獨立於Node,它們都有擁有本身獨立的源碼以及證書。Node僅僅是使用它們。因此你須要記住這些,若是你想知道你的程序運行在什麼地方。若是你正在處理數據壓縮相關的事情,你可能會在zlib庫底層堆棧遇到遇到麻煩,那麼你將面對一個zilb相關的錯誤,而不是歸責於Node。

問題 #5: Node可否不依賴於V8運行?

這多是一個棘手的問題。 你確實須要一個VM來運行Node進程,可是V8並非惟一可使用的VM。 您可使用Chakra

問題 #6: module.exportsexports有什麼不一樣 ?

你能夠一直使用module.exports去導出模塊的API。除一種狀況外,你也可使用export:

module.exports.g = ...  // Ok

exports.g = ...         // Ok

module.exports = ...    // Ok

exports = ...           // Not Ok
複製代碼

爲何?

export僅僅是module.export的一個別名或引用。 更改導出時,你將更改該引用,而再也不更改官方API(即module.exports)。 你只須要在模塊做用域中獲取局部變量便可。

問題 #7: 爲何頂級變量不是全局變量?

若是你在模塊module1中定義了一個頂級變量g:

// module1.js

var g = 42;
複製代碼

同時你有一個模塊module2引用了模塊module1,而且嘗試訪問變量g,你將獲得g is not defined.的錯誤。

爲何?

若是你在瀏覽器端作相同的事情,你能夠在該定義該頂級變量腳本以後的全部腳本里訪問該頂級變量。 每一個Node文件在後臺都有其本身的IIFE(函數調用表達式)。 在Node文件中聲明的全部變量都做用於該IIFE。

相關問題: 下面這個僅僅含有一行代碼的Node文件將會輸出什麼?

// script.js

console.log(arguments);
複製代碼

你將會看一些參數!

爲何?

由於Node執行的是一個函數。Node將你的代碼包裝到一個函數中。該函數中明肯定義了上圖中你所見的5個參數。

問題 #8: 這些對象:exportrequiremodule都是全局可用的,然而在它們在每一個文件又有所不一樣,爲何?

當你須要使用require對象,你就是像一個全局變量那樣直接使用它。然而,若是你在兩個不一樣的文件中檢查require,你將看到兩個不一樣的對象。爲何? 由於 因爲具備相同的IIFE魔法:

如你所見, 「這IIFE魔法」向你的代碼中傳遞如下五個參數: exports, require, module, __filename, 和 __dirname。當你在Node中使用這5個參數時,它們看起來像全局變量,但實際上它們僅僅是函數參數。

問題 #9: 什麼是Node中的循環依賴?

若是你定義一個模塊module1引用了模塊module2,同時模塊module2內部又引用了模塊module1。將發生什麼?報錯?

// module1
require('./module2');

// module2
require('./module1');
複製代碼

你不會獲得報錯。由於Node容許那種狀況。 因此模塊module1引用模塊module2,可是由於模塊module2依賴模塊module1且模塊module1沒有加載完成,模塊module1將僅僅獲取到模塊module2的一個部分版本。程序將提示警告。

問題 #10: 何時適合使用文件系統的同步方法(如 readFileSync)?

Node模塊fs中的每個方法都有一個同步版本。爲何你會使用一個同步方法代替一個異步方法?

有時候,使用同步方法會更好。好比服務器仍在加載時,能夠在任何初始化步驟中使用它。 一般狀況下,初始化步驟以後執行的全部操做都取決於在那裏獲取的數據。 只要你使用同步方法是一次性的,就可使用同步方法來避免引入回調獄。

可是,若是您在處理程序(例如HTTP服務器請求回調)中使用同步方法,那簡直就是100%錯誤。 不要那樣作。

我但願你可以回答以上部分或者所有的問題。

感謝閱讀。

相關文章
相關標籤/搜索