Node.js 是一個 JavaScript 執行環境 (run-time environment)。原本 JavaScript 檔案只能在瀏覽器上被執行,但是當我們在電腦上安裝了 Node.js 之後,就可以執行副檔名為 .js 的 JavaScript 的檔案。
有了 Node.js,JavaScript 就像其他你聽過的程式語言一樣 (例如,PHP、Python、Ruby、Java) ,都擁有能夠開發網路應用程式的能力。有了 Node.js,JavaScript 可以直接建立、讀取、修改和刪除電腦內的檔案,並且能和資料庫連通。另外 Node.js 也添加了網路應用程式需要的功能,例如作為伺服器處理用戶端發出請求 (request) 並給予回應 (response) 等。
隨著 Node.js 的問世,前端開發者的觸角也逐漸蔓延到後端,甚至透過 Electron.js 這類強大的套件,也可以製作出完整的桌面 GUI 應用程式;藉由 Node.js,前端開發者得以使用較為熟悉的 JavaScript 為敲門磚,逐步的拓展自己的技術守備範圍。但為什麼為了網頁而生的語言可以透過 Node.js 跑在伺服器端呢?要解開這個問題,就得從認識 Node.js 出發。
Node.js 是什麼?
根據官網的說法:
Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine.
「runtime」 指的是執行環境,就如同網頁上的 JavaScript 是在瀏覽器的 JavaScript 引擎上執行,Node.js 就是一個能執行 JavaScript 的環境,而 V8 則是主流瀏覽器 – Google Chrome 的 JavaScript 引擎,負責解析、執行 JavaScript,也就是負責實踐 ECMAScript 規範中定義的部份;另外,V8 是開源的專案,有興趣的讀者可以參照 Google Git – V8。
Node.js 以 V8 為核心,加上一系列 C/C++ 的套件,成功的讓 Server 端也可以執行 JavaScript。
Node.js 優點
但是,後端語言已經這麼多了,為什麼還要大費周章的將 JavaScript 移植到 Server 端呢?
這是因為 JavaScript 是一個事件驅動的語言,透過事件迴圈,能讓執行緒幾乎不會被卡住;而這樣的特性,非常適合用來接收高併發(High Concurrency)的請求。
例如在傳統的伺服器中,每個使用者的連接都會產生一個新的執行緒(看實作,不一定),並佔據一定的效能,伺服器在高併發的情況下,很容易就會由於應接不暇而無法服務新的流量;但 Node.js 會將每個 request 變成事件迴圈中待處理的事件,主執行緒只負責承接、轉拋、回應,並持續的在事件迴圈中循環,一切都以事件為核心在驅動程式運行,自然也就不會出現執行緒卡死的現象。
當然,如果是商業邏輯複雜的後端程式,效能瓶頸不在流量的服務,Node.js 就無用武之地;但在設計需要承接高流量,且處理邏輯不太複雜時,Node.js 可能就會是個可以考慮的選項。
Node.js 功能
前面提到,Node.js 就是一個可以執行 JavaScript 的環境,而這個環境除了提供瀏覽器 Web API 實作的 setTimeout、setInterval、console 之外,也因為執行環境不同,有另外一系列的 API 供開發者使用,例如可以讀寫檔案的 fs、處理網路請求的 http、做加解密雜湊處理的 crypto、設定叢集的 cluster 等等。
詳細的使用說明,可以參考 Node.js 的官網文件
事件迴圈
由於 JavaScript 擁有單執行緒的特性,且為了讓執行緒不會被需要等待的同步程式卡住,必須透過事件迴圈的機制來實現這個目標。如果各位讀者還有印象,我們在 這篇 有聊過瀏覽器中 JavaScript 的事件迴圈,其中有很大一部分是由瀏覽器完成的;在 Node.js 中則透過 libuv 來實現這部分的機制。不同於瀏覽器的事件迴圈,Node.js 中的事件迴圈大致會有以下幾個階段:
- timers:執行 setTimeout setInterval 給的 callback
- pending callbacks:執行被延遲到下一個事件迴圈的 I/O Callback
- idle, prepare:Node.js 內部專用的階段
- poll:檢索新的 I/O events,執行 I/O callbacks
- check:執行 setImmediate 給的 callback。
- close callbacks:執行關閉資源的 callback,例如 socket.on(‘close’, …)
相對於瀏覽器的事件迴圈多了好多個階段,但其實只是把所有的 callback 分成了四種:timers、I/O events、immediates、close handlers,並依照順序輪流執行,其他在概念上還是一樣的:每個階段有自己的 Queue,輪到它時清空 Queue,到下個階段,周而復始。
為避免主執行緒阻塞,poll 階段可以設定執行上限,到達上限時就會將 Queue 內的東西移交到 pending callbacks 階段的 Queue 中,下一個事件迴圈時再接續執行。
比較需要注意的地方是,微任務佇列(microtask queue)在每個階段結束後都會執行、清空,順序是先清空 process.nextTick 的 callback,再執行其他的如 Promise 的 callback。
常有人誤解 process.nextTick,會想問例如「一個 Tick 是多久?」之類的問題,但其實 Tick 指的就是事件迴圈中的一個階段,因此時間是不固定的喔!
套件管理工具 npm
在現代的網站中,使用他人開放原始碼的套件輔助開發已經是稀鬆平常的事情,無論是透過套件加速堆砌產品,或是在開發環境中加上協助工程師的各式工具,只需要稍加設定,一個專案便能輕易加載了成千上萬的外部程式;但如此方便的機制,究竟是怎麼實現的呢?
對前端開發者來說,最熟悉的應該是安裝 Node.js 時自動附帶的 npm
npm 即為 Node Package Manager 的縮寫,開發者可以透過 Node 隨附的 npm cli,進行套件的安裝及管理。
例如在專案資料夾的終端機中輸入 npm install express,npm 便會自動從 Registry 中尋找 express 這個非常熱門的 Node.js Web Server 框架,取得最新版本,下載到專案中的 node_modules 資料夾中。
然而在專案中,不可能每次都透過開發者自行指定套件安裝,不但無法管理,也很沒有效率;開發者可以透過專案中的 package.json,羅列出專案需要哪些套件,之後安裝時只需要 npm install,npm 便會自動依照 package.json 的內容下載套件。
npm install 的執行過程
在透過這些套件管理工具進行安裝時,背後的機制究竟是什麼呢?下面將執行 npm install 的過程拆成五個步驟,也許各家套件管理工具實作會略有不同,但不外乎都會經過這些階段:
1. 計算缺少的套件
npm 會從專案中的三隻檔案,計算出本次 npm install 需要重新下載安裝的內容:
- 專案內的 node_modules 結構
- 開發者設定的 package.json
- npm install 後自動生成的 package-lock.json
由於 package.json 中的套件版本可能會是使用 Semantic Versioning 描述的,npm 需要以 package.json 描述的版本為基礎,與 node_modules 及 package-lock.json 相互比對後,才能計算出需要更新的套件。
2. 從 Registry 取得套件資訊
計算出來缺少套件列表後,npm 向指定的 Registry 獲取各目標套件的 package.json、查詢可用版本,並解析出下載 URL。
3. 計算差異
由於套件本身也是專案,也可能引用其他套件,不同的套件引用到相同的套件這種事,自然也是稀鬆平常;npm 在這步驟會去計算各套件的 package.json,整理各套件個別需要下載的版本(有可能同套件需要多版本)最後產出整個專案所使用的的套件結構樹(package-lock.json)。
4. 下載、提取真正需要的套件
有了前面這麼多步驟的整理,接著就開始依序下載套件,並將下載的內容解壓縮,提取到 node_modules 資料夾中;這個步驟是 npm install 需時最長、最耗效能的步驟,主要是因為 下載、解壓縮、寫入硬碟分別需要網路、CPU 及硬碟 IO 的支撐,只要硬體設備的其中一環資源較缺乏,開發者馬上就會有感,速度自然也就快不起來。
為了解決這個問題,npm 本身也擁有本地快取機制,在寫入到 node_modules 時,同時會寫入一份到電腦的本地快取中,未來如果有其他專案需要用到同一個版本的套件,npm 會在向 Registry 確認版本未更新(ETag 相符,回傳 304)後,直接複製快取的套件到 node_modules。
小結
現代前端開發透過 npm 安裝套件,並經過 打包工具 的處理,在 前端三大框架 的普及之下,都已經是基本功了;但使用工具的同時,還是要理解為何而用,以及其背後的機制,才能夠真正的控制並活用工具。
知名的網路公司也都使用 Node.js
根據Stackshare的資料,有超過7000家公司都在使用 Node.js,包含了知名的網路公司 Netflix、Uber、Paypal、Linkedin等,是業界非常廣泛使用的後端執行環境。
延伸:熱門的 Node.js 工具有哪些?
Node.js 是一個非常流行的 JavaScript 運行環境,可以用於構建高效的後端應用程序。以下是一些熱門的 Node.js 工具,以及它們的主要用途:
1. Express.js:這是一個輕量級且靈活的 Web 應用程序框架,可以用於構建 RESTful API 和 Web 應用程序。
2. Socket.io:這是一個用於實時 Web 應用程序的 JavaScript 库,可以用於建立即時通信和實時數據傳輸。
3. NPM:這是 Node.js 的包管理器,可以用於安裝、升級和管理 Node.js 模塊。
4.PM2:這是一個進程管理器,可以用於在生產環境中管理 Node.js 應用程序,包括自動重啟、群集模式和負載均衡等功能。
5. Mongoose:這是一個 MongoDB 對象數據映射器(ODM),可以用於在 Node.js 中與 MongoDB 數據庫進行交互。
6. Winston:這是一個用於 Node.js 的日誌庫,可以用於記錄應用程序的日誌信息。
7. Passport.js:這是一個用於身份驗證的 Node.js 库,可以用於實現本地、社交和多因素身份驗證策略。
8. Puppeteer:這是一個用於化測試和網頁爬蟲的 Node.js 库,可以用於模擬瀏覽器行為和操作 DOM。
這些 Node.js 工具可以幫助開發人員更高效地開發和管理 Node.js 應用程序,並提供各種有用的功能,例如 Web 應用程序框架、進程管理、數據庫交互、日誌記錄、身份驗證和自動化測試等。
結語
我們從最基本的介紹出發,認識了 Node.js 這個 Server 端的 JavaScript 執行環境,並提到 Node.js 的語言特性在高併發情況的優勢,最後重點理解 JavaScript 的重要特色:事件迴圈,背後是如何在伺服器端執行。
(本文作者是 ALPHA Camp「JavaScript 全端開發課程」助教、前端工程師 Gary,擁有四年以上軟體開發經驗。本文轉載自前端三十系列文)