[譯] 你不知道的 Node

在今年的 Forward.js 大會(一個 JavaScript 峯會),我進行了一場主題爲「你不知道的 Node」 的演講,在那場演講中,我問了現場觀衆一系列關於 Node.js 運行時的問題,然而大部分搞技術的聽衆都不能所有回答得上。前端

我當時並無真的計算過,直到演講完了纔有一些勇敢的人過來跟我坦白說他們不會。node

這個問題正是讓我發表演講的緣由,我並不認爲咱們教授 Node 的方式是對的。大多數關於 Nodejs 的教材內容主要集中在 Node 包和 Node 運行時以外的地方,大多數這些包都在 Node 運行時封裝好了模塊(例如 httpstream),問題多是藏在運行時裏面,然而你不懂 Node 運行時的話,你就麻煩了。react

問題:大多數關於 Nodejs 的教材內容主要集中在 Node 包和 Node 運行時以外的地方。android

我挑選了幾個問題並組織了一些答案來寫成這篇文章,答案就在問題的下面,建議嘗試先本身回答。ios

若是你發現了錯誤或誤導性的回答,請跟我聯繫。git

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

調用棧百分之百就是 V8 的一部分,它是 V8 用來追蹤方法調用的數據結構。每一次咱們調用一個方法,V8 在調用棧中放置一個該方法的引用,而且 V8 對每一個其餘方法的嵌套調用也這樣操做,同時也包括那些自身遞歸調用的方法。github

Screenshot captured from my Pluralsight course — Advanced Node.js
Screenshot captured from my Pluralsight course — Advanced Node.js

當方法的嵌套調用結束時,V8 會逐個地將方法從棧中 pop 出來,並在它的位置使用方法的返回值。後端

爲何這對於理解 Node 是如此關鍵?由於在每一個 Node 進程中你只有一個調用棧。若是你令調用棧處於忙碌,你整個的 Node 進程也將變得忙碌。牢記這一點!瀏覽器

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

你以爲事件循環在這張圖的哪一個部分?bash

Screenshot captured from my Pluralsight course — Advanced Node.js
Screenshot captured from my Pluralsight course — Advanced Node.js

答案是 libuv 。事件循環不是 V8 的一部分!

事件循環是操控外部事件並將它們轉換爲回調調用的實體,它是從事件隊列中取出事件並將事件的回調函數推動調用棧的一個循環。而且該循環過程當中分爲多個獨立的階段。

若是這是你第一次據說事件循環,這些概念對你可能幫助不大。事件循環是一副很大的輪廓圖的其中一部分:

Screenshot captured from my Pluralsight course — Advanced Node.js
Screenshot captured from my Pluralsight course — Advanced Node.js

你須要先理解這幅輪廓圖再理解事件循環,你須要先理解 V8 在這裏面飾演的角色、理解 Node APIs 並知道事件是怎樣進入隊列並被 V8 處理的。

Node APIs 是像 setTimeoutfs.readFile的一些方法,它們不是 JavaScript 自己的一部分,它們就是 Node 提供的方法。

事件循環在這張圖片的中間(一個更復雜的版本,真的)飾演一個組織者的角色。當 V8 調用棧爲空的時候,事件循環能夠決定接下來執行什麼。

問題 #3:當調用棧和事件循環隊列都爲空時,Node 會作什麼?

Node 會直接退出。

當你執行一個 Node 程序時,Node 會自動地開始事件循環,當沒有事件處理時而且沒有其餘任務時,Node 則會退出進程。

爲了保持一個 Node 進程持續運行,你須要把一些任務放入事件隊列中。例如,當你建立一個計時器或一個 HTTP 服務器時,你基本上就是在告訴事件循環要保持並檢測這些任務持續執行。

問題 #4:除了 V8 和 Libuv,Node 還有哪些外部依賴?

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

  • http-parser
  • c-ares
  • OpenSSL
  • zlib

對 Node 自己來講,上面這些庫都是外部的,這些庫都有本身的源代碼、許可證,Node 只是使用它們而已。

你想記住它們是由於你想知道你的程序執行到哪裏了,若是你在作一些數據壓縮的工做,有多是在 zlib 這個庫遇到問題,Node 是無辜的。

問題 #5:不用 V8 有可能運行一個 Node 進程嗎?

這多是一個奇技淫巧的問題。你確定是須要一個虛擬機去執行 Node 進程,但 V8 並非惟一的虛擬機,你還可使用 Chakra。

查看這個 Github 倉庫來跟蹤 node-chakra 項目的進度:

問題 #6:module.exports 和 exports 二者的區別?

你可使用 module.exports 導出模塊的 API,你也可使用 exports,但有個值得注意的地方:

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

exports.g = ...         // Ok

module.exports = ...    // Ok

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

爲何?

exports 只是一個對 module.exports 的引用或別名,當你修改 exports 時你實際上是在無心中試圖修改 module.exports,但修改對官方 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</pre>

console.log(arguments);複製代碼

你會看到一些參數!

爲何?

由於 Node 執行的是一個函數。Node 將你的代碼包裹在一個函數中,這個函數明確地定義了你上面看到的那 5 個參數。

問題 #8:exportsrequire、和 module三個對象在每一個文件中都是全局可用的,但他們在每一個文件中又有區別,爲何呢?

當你須要使用 require 對象時,你只是像使用全局變量那樣直接使用它,然而,若是你在 2 個不一樣的文件中比較 require 對象的區別,你會發現 2 個不一樣的對象,怎麼回事?

仍是由於同樣的緣由 IIFE(當即調用函數表達式):

正如你所見,IIFF 將如下 5 個參數傳遞到你的代碼中:exports, require, module, __filename, and __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 服務器的 on-request 回調函數裏使用同步方法,那就真的是 100% 錯誤!別那樣作。

我但願你能答上一部分或者全部的問題,如下是我寫得比較深刻 Node.js 細節的文章:


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索