作為還在漫漫前端學習路上的一位自學者。我以學習分享的方式來整理自己對於知識的理解,同時也希望能夠給大家作為一份參考。希望能夠和大家共同進步,如有任何紕漏的話,希望大家多多指正。感謝萬分!
在 Node.js 系列的第一節裡, 我會先介紹 Node.js 的一些基本概念. 讓你在看完這篇文章時, 能對 "什麼是 Node.js?", "為什麼用 Node.js?" 這兩個問題有個基本的概念. 並且嘗試寫下第一句 Node.js 程式碼.
什麼是 Node.js?
先來看看 Node 官網 給的答案:
Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行環境。 Node.js 使用了一個事件驅動、非阻塞式 I/O 的模型,使其輕量又高效。
基本上, 很多人第一次看到上面這段定義的時候, 完全搞不懂它在說什麼...
下面我就來逐步解釋 Node.js 的這段定義.
基於 Chrome V8 引擎的 JavaScript 執行環境
看到這句話後, 你可能第一個疑問是 ? "什麼是 Chrome V8 引擎?"
在解答這個問題之前, 回到我們熟悉的前端領域, 我先問另一個問題 ? "JavaScript 程式碼是如何在瀏覽器中執行的?"
簡單來說, 我們所寫的 JavaScript 原始碼, 是為了給人看的, 瀏覽器載入 JavaScript 程式碼後, 它是看不懂的, 需要翻譯成機器碼, 也就是 "機器的語言", 才可以讓機器執行. 在瀏覽器中, JavaScript 引擎負責進行程式碼的解釋. V8 引擎是 Chrome 瀏覽器在用的 JavaScript 引擎. V8 引擎由 C++ 實現, 支援跨平臺, 就是說可以在各種作業系統上使用, Node.js 基於 V8 引擎, 意味著它可以讓 JavaScript 程式碼脫離瀏覽器的束縛, 在各種各樣不同的作業系統上執行.
那麼一句話總結, Node.js 不是一門語言, 是一個可以讓 JavaScript 程式碼在各種各樣的平臺上得到執行的執行環境. 之所以支援 JavaScript 語法是因為基於 V8 引擎.
非阻塞式 I/O
"非阻塞式 I/O", 我知道這幾個字看起來讓人有點懵. 彆著急, 那讓我換個說法, "不會阻塞 JavaScript 程式執行的 Input/Output 操作", 這樣會不會清楚一點? 可能你還是不太懂, 那下面我就逐字地解釋.
在說 "非阻塞" 之前, 先了解什麼是 "阻塞". 從字面上理解, "阻塞" 就是堵住了, 通不過的意思. 在計算機中, 程式線上程內按順序被執行, 後面的操作必須等前面的操作結束才能被執行. 如果前一個操作耗時很長, 後面的操作就要一直等著, 直到前面的操作完成. 這個等待的狀態, 叫做 "阻塞".
執行緒: 程式執行流的最小單元。程式程式碼在其中被 CPU 依次處理. 在一條執行緒中, 同一時間, 只有一段程式碼被被執行, 或等待被執行.
現在再來說說什麼是 "Input/Output 操作". 從字面上翻譯就是 "輸入/輸出", 那輸的是什麼呢? 簡單說就是 "資訊". 在程式執行過程中如果需要作業系統進行 磁碟讀寫 或 網路通訊, 我們就都統稱為 "I/O 操作". 例如, 從伺服器獲取頁面, 寫入檔案, 提交表單, 讀取資料庫都是屬於這個範疇的. 凡是這一類操作, 直觀感受是 "花的時間較長". 那麼在程式碼執行 I/O 操作時, 往往要讓後面的操作等很長時間. 整條執行緒一直處於阻塞的狀態, 這種 I/O 操作方式稱為 "阻塞式 I/O". 拿生活舉例, 遊戲沒下載完, 我就只能乾等著玩不了.
在程式設計中, 對於高併發的任務 (同時發生的任務) , 線上程阻塞的情況下, 整條執行緒不能執行程式, 這會導致任務處理速度極慢. 常見的方案是通過多執行緒來解決. 但每條執行緒的利用率並沒有增加, 同時也會導致硬體成本高昂.
併發: 在同一個時間段中, 幾個任務同時進行. 但是在任意時間點, 有且只有一個任務在執行.
(可以想象成你在同時寫五份作業, 每份只寫幾筆就換下一個, 然後再反回來接著寫兩筆. 雖然五份作業在一段時間內同時都被寫了, 但是在任一時間點你只是在寫其中的一份作業.)
在 Node.js 中, JavaScript 程式碼在一個單一的主執行緒中進行處理, 同一時間只能處理一項任務. 為了處理高併發, 採用了 "非阻塞式 I/O", 也可以稱為 "非同步式 I/O". 當執行緒遇到 I/O 操作時,不會以阻塞的方式去等待操作完成. 而是將 I/O 操作先放到另一個地方等待處理, 然後 Node.js 繼續執行下一條程式碼. 等主執行緒程式碼全部執行完畢後, 再去處理 I/O 操作. 具體的過程, 在下一段介紹.
事件驅動
繼續上一段的內容. 在 Node.js 中, 當主執行緒遇到 I/O 操作時,會先把 I/O 操作封裝成一個請求物件, 然後被推入到執行緒池中等待執行. 然後非同步呼叫的第一階段結束, JavaScript 主執行緒繼續執行後續操作, 直至所有非阻塞操作都處理完. 因為 I/O 操作線上程池中等到處理, 所以也不會阻塞主執行緒.
當執行緒池有空餘執行緒時, 等待的 I/O 操作會被處理. 當 I/O 操作完成, 獲取的結果儲存在請求物件的 result
屬性上. 作業系統會來檢查執行緒池是否有處理完的請求, 如果有, 就把請求物件加入到事件佇列中.
在主執行緒的所有非阻塞程式碼都處理完後, 事件迴圈開始逐個處理事件佇列中的事件. 儲存在請求物件的 result
屬性被取出, 作為與請求繫結的回撥函式的引數. 然後執行回撥來完成此 I/O 操作.
當佇列中已經沒有未處理的事件了, 程式結束. 可以看出程式的結束與否, 取決於事件是否被全部處理完畢, 因此稱 Node 是事件驅動的.
做個比喻
如果不好理解的話, 可以想象老王一個人去釣魚.
- 『 單執行緒阻塞式 I/O 』就像老王把魚竿架好之後, 就在旁邊等著, 等魚漂抖動的時候就拉桿. 在等待的這段時間裡, 他是什麼也不幹的. 當前一條魚上鉤後, 老王才能釣下一條魚, 或者做其他事情.
- 『 多執行緒阻塞式 I/O 』過了幾天, 老王覺得這樣效率低, 就買了十把魚竿同時架起來. 在同一段時間內, 這種方法釣的魚的確多了, 但老王在一個時間點只能去操縱一個魚竿, 單個魚竿的效率很低, 老王仍舊一直守在魚竿旁, 在等的時候不能做其他的事.
- 『 非阻塞式 I/O 』又過了幾天, 老王一個發明家朋友給了他一個 "自動通知魚竿", 魚上鉤後, 魚竿會給老王發微信通知他過來收魚. 這樣老王就可以在等魚上鉤的時間裡去幹別的事情了.
為什麼用 Node.js?
先來說 Node.js 的優點:
- 跨平臺性: Node.js 基於 V8 引擎, 使得其可以覆蓋到日常所能見的大多數平臺.
- 適合 "資料密集型實時應用": Node.js 採用非阻塞式 I/O 模型, 非常適用於處理涉及大量 I/O 操作的資料密集型應用. 比如, 聊天室, Web 伺服器等.
- "前端開發者" 學習成本低: Node.js 使用 JavaScript 語法, 對於前端開發人員來說, 學習成本非常低.
- "第三方模組" 豐富: 繁榮的 Node.js 社群 提供了豐富的第三方模組供其使用. 通過這些模組, Node.js 可以擁有更多的應用場景.
當然 Node.js 也是有缺點的:
- 不適合 "CPU 密集型應用": Node.js 不適合處理涉及大量 CPU 計算的任務. 比如, 視訊編碼、人工智慧等.
- 不能充分的利用多核 CPU 伺服器: Node.js 是單執行緒的, 在多核裝置上執行, 不能充分利用其效能. 但是 Node.js 其實也提供了支援多執行緒的方法.
安裝 Node.js
根據計算機平臺的不同, 安裝方法也略有差異. 本文側重於概念, 具體實操, 可能每位讀者因偏好不同, 方法各異. 在此就不再贅述了.
最方便的方式是直接上官網去下載對應平臺的原始碼或安裝包, 或者通過包管理器直接安裝.
Node.js 程式碼例項
var http = require('http');
function myNodeServer(req, res){
res.writeHead(200, {'Content-type':'text/plain'});
res.write('Hello World');
res.end();
}
http.createServer(myNodeServer).listen(3000); //監聽 3000 埠
console.log('Server is running!');
複製程式碼
建立一個空檔案, 把上面程式碼複製到其中, 檔案字尾改成 .js
. 然後在命令列中, 用 node
命令執行剛剛的檔案 ( 注意檔案所在目錄位置和檔名 ) .
如果執行成功你的命令列上會顯示 "Server is running!" 這段話. 然後用瀏覽器訪問 http://localhost:3000/
這個地址. 你會看到網頁上顯示 "Hello World". 至此你就實現了一個簡易 HTTP 伺服器.
Node.js 學習資源推薦
? 好啦,今天的分享就告一段落啦。下一篇中,我會介紹 Node.js 的模組機制。
傳送門 - Node.js 系列 - 模組機制
如果喜歡的話就點個關注吧!O(∩_∩)O 謝謝各位的支援❗️