你並不知道Node
在今年 Forward.js,JavaScript的一個會議中,我做了主題為「你並不知道 Node」的演講。在這個演講中,我向觀眾提出一些關於 Node.js 運行時的具有挑戰性的問題,大多數技術相關觀眾無法回答其中的大部分問題。
我並沒有進行實際的統計,但是在那個房間感覺是這個樣子的。並且有一些勇敢的觀眾在演講之後向我承認了這一事實。
這也就是我做此次演講的原因。我不認為我們以正確的方式教學 Node!大多數的 Nodejs相關的教學概念集中於Node 包卻不是它的運行時。
大多數的包都將 Node 運行時包裹在模塊中(比如 http 或者 stream)。當你遇到問題的時候,這些問題可能存在於運行時之中,如果你不知道Node 運行時的話,你就陷入麻煩了。
這個問題:多數的 Nodejs相關的教學概念集中於Node 包卻不是它的運行時
我為這篇文章選擇了一些問題並且做出回答。它們將以標題的形式在下面呈現。首先嘗試在你自己心中做出回答!
如果你發現錯誤或者具有誤導性的回答,請讓我知道。
問題1: 什麼是調用棧並且它是V8的一部分么?
調用棧當然是 V8 的一部分。它是 V8 用來追蹤函數調用的一種數據結構。每次我們調用一個函數的時候,V8都會將向函數調用棧中壓入一個對於函數的引用,它對於其它函數的內嵌函數也會這樣做。這也包括遞歸調用函數的函數。
當函數中的內嵌函數到達末端的時候,V8 就會每次彈出一個函數並且在它的位置使用返回值。
為什麼理解 Node 是重要的?因為你在每個 Node 進程中只能獲取一個調用棧。如果你讓這個調用棧保持忙碌,那麼你的整個 Node 進程也會是忙碌的。記住這一點。
問題2: 什麼是事件循環? 它是V8的一部分嗎?
你認為事件循環是在這張圖的什麼地方?
事件循環是由 libbuv 庫提供,它不是 V8 的一部分。
事件循環處理外部事件並且將它們轉換成回調調用。它是一個從事件隊列中跳去事件的循環並且將它們的回調壓入到調用棧中。它也是一個多相回調。
如果這是你第一次聽說事件循環,這些概念將可能不會那麼有用。這個事件循環是一張更大的圖的一部分。
你需要理解這張更大的圖從而理解事件循環。你需要理解 V8 的角色,知道 Node 的 API,並且知道事件如何進入隊列從而被 V8 所執行。
Node 的 API 就是一些函數,比如 setTimeout 或者 fs.readFile。這些並不是 JavaScript 中的一部分。它們是由 Node 提供的函數。
事件循環位於這張圖(真的是一個更複雜的版本)的中間位置,就好像是一個組織者。當 V8 調用棧為空的時候,事件循環可以決定下一步執行哪一個。
問題3: 當調用棧以及事件循環都為空的時候,Node會做什麼?
很簡單,它會退出。
當你運行一個Node程序的時候,Node 將會自動開始事件循環並且當事件循環變為idle 的時候並且沒有其它的事情需要做的時候,這個進程將會退出。
為了保持 Node進行運行,你需要在事件隊列的某個地方放一些東西。比如,當你啟動一個計時器或者一個 HTTP 服務的時候,你基本上就是告訴事件循環保持運行並且檢查這些事件。
問題4: 除了V8以及Libuv,Node還有哪些外部依賴?
下面的是 Node 進行需要的所有的單獨庫:
http-parser
c-ares
OpenSSL
zlib
它們對於 Node 來說都是外部依賴。它們具有它們自己的源代碼。它們具有它們自己的證書。Node 只是使用它們。
你希望記住因為你想知道你的程序在什麼地方運行。如果你在處理數據壓縮,你可能遇到一些 zlib 庫使用的一些困難。你可能在解決一個 zlib 的 bug。不要把所有的事都怪罪於 Node。
問題5: 可以不使用V8來運行 Node?
這可能是一個棘手的問題。你需要一個 VM 來運行 Node 進程,但是 V8 並不是你唯一可以試用的 VM。你可以使用 Chakra。
獲取 Github倉庫來跟追蹤 node-chakra 的進程:https://github.com/nodejs/node-chakracore
問題6: module.exports 和 exports 的區別是什麼?
你可以總是試用 module.exports 來導出你模塊的 API。你也可以exports 除了一種情況:
為什麼?
exports只是一個對於 module.exports 的引用或者別名。當你改變 exports 的時候,你是在改變那個引用而不是改變官方的 API(module.exports)。你將只會獲得一個模塊作用域內的局部變數。
問題7: 頂級變數怎麼不是全局變數?
如果你有一個 module1 定義了一個頂級變數 g:
並且你有一個 module2 引入了 module1 並且嘗試訪問變數 g,你將會發現 g 是未定義的。
*為什麼?*在瀏覽器中你如果做同樣的操作,你能夠在所有的變數在定義之後被引入就可以訪問頂級變數。
每一個 Node 文件都會在背後獲取它自己的立即執行函數表達式(IIFE)。所有在這個 Node 文件裡面定義的變數都是在這個 IIFE 作用域內。
相關問題:運行下面僅僅包含一行代碼的 Node 文件的輸出
你將會看到一些參數!
為什麼?
因為 Node 執行的是一個函數。 Node 將你的代碼使用函數來包裝並且這個函數準確定義了上面你看到的5個參數。
問題8: 對象exports,require和module都是在每個文件中全局可用,但每個文件都不同。這是如何實現的?
當你需要使用 require 對象的時候,你就可以直接使用它就好像是一個全局變數。然而,如果當你在不同的兩個文件中檢測 require,你將會看到兩個不同的對象。這是如何實現的?
Because of that same magic IIFE:
都是因為相同的神奇的 IIFE:
正如你所見,神奇的 IIFE 將你的代碼傳遞到5個參數之中:exports,require,modue,__filename 以及 __dirname。
當你在 Node 試用這5個參數的時候,它們看起來是全局的,但是事實上它們僅僅是函數的參數。
問題9: Node 中的循環模塊依賴是什麼?
如果你有一個 module1 引入 module2 並且同樣 module2 也引入了 module1,那麼將會發生什麼?一個錯誤?
你將不會得到一個錯誤。Node 允許這種情況。
那麼在 module1 引入 module2 的時候,但是因為 module2 需要 module1 並且 module1 尚未完成,module1 將會僅僅獲取 module2的一個部分版本。
你將被警告。
問題10: 什麼時候試用文件系統中的同步方法 (比如 readFileSync)是可以的?
Node 中的每一個 fs 方法都有一個同步的版本。為什麼你要使用一個同步方法而不是一個非同步方法呢?
有時候試用同步方法是不錯的。比如,在初始化步驟中伺服器依然在載入的情況下使用同步方法。大多數情況是初始化步驟之後的所有事取決與在初始化步驟中獲取的數據。
在不引入回調的層級,使用同步方法是可以接受的,只要你使用同步方法是一次性的事情。
然而,如果你在一個處理程序中試用同步方法,比如 HTTP 伺服器請求回調,那將會很明顯 100% 報錯。不要那樣做。
如您有何疑問,請聯繫小編:
15201480058
※親,你坐過站了嗎?
※互聯網風口,程序員怎麼飛?
※這個電商網站不止是看起來很好吃
※每個程序員都該知道的五大定理
TAG:優才學院 |