--- tags: Node.js --- # Node.js ## Node.js 是什麼 Node.js 不是一門語言,也不是框架或庫,他僅僅只是一個 JS 運行時環境(即他像瀏覽器一樣 可以解析、執行 JS 代碼)。 - 瀏覽器中的 JS 是 EcmaScript 與 BOM、DOM - Node.js 中的 JS 則沒有 BOM 和 DOM,只有 EcmaScript (因為服務端不需處理 BOM 和 DOM) - 但 Node.js 中的 JS 提供了很多有關服務器操作級別的 API,如:文件讀寫、網路請求、創建 HTTP 服務器等 Node.js 是基於 Google Chrome V8 引擎獨立開發的,該引擎是目前最高效能的 JS 引擎。 另外 NPM 是基於 Node.js 開發出來的包管理工具,也是世界上最大的開源庫生態系統 ## Node.js 有兩個特性: 1. 事件驅動 2. 非阻塞 I/O 模型(異步) ## 學習 Node.js 可以學到什麼 1. B/S 編程方式 2. 模塊化編程(require 方法) 3. 異步編程(EX: Promise callback) 4. Node.js API 5. Express Web 框架 ## 安裝 先打開終端機 輸入命令 `node -v` 確認電腦是否已有安裝 Node.js 如果沒有出現版本號 則表示當前電腦沒有安裝 這時候就請到官網下載 [Node.js](https://nodejs.org/zh-tw/) 若要更新 最簡單、最快的方式也是直接到 [Node.js 官網](https://nodejs.org/zh-tw/)點擊安裝,就會直接覆蓋升級 - LTS : 長期支持版(穩定維護中) - Current : 最新版 安裝後再次打開終端機 輸入命令 `node -v` 即可確認電腦是否安裝完成 ## 基礎使用方式 創建一個資料夾 `demo` 然後在資料夾中新增 `hello.js` 文件 文件內容為 `console.log('Hello')` 打開終端機到 `demo` 目錄 直接輸入 `node hello.js` 它就會回傳 `Hello` 了 - 注意事項:要執行的 JS 文件嚴禁取名為 `node.js` 最好也不要取中文名。 - 只要是 `.js` 檔案就可以使用命令 `node xxx.js` 來執行 ## fs 核心模塊 在瀏覽器中沒有操作文件的方法 但在 Node.js 中可以做到,通過引入核心模塊 `fs` 即可實現。 創建一個 `read.js` 文件 輸入以下代碼: ```javascript= // 引入文件核心模塊 const fs = require("fs"); // 使用 fs.readFile 方法讀取文件 // 參數1 是要讀取的文件路徑 // 參數2 是回調函數 回調中第一個參數是錯誤對象 第二個是文件內容 fs.readFile("./README.md", (err, data) => { // 由於文件默認存儲二進制數據 // 然後 Node.js 本身又會把它轉成十六進制 所以輸出結果是十六進制 // 解決看不懂十六進制的問題:使用 toString 方法把 data 轉回正常文字 console.log(data.toString()); }); ``` 最後開啟終端機 輸入 `node read.js` 即可得到 `README.md` 的文件內容 ## http 核心模塊 使用 Node.js 的核心模塊 http 來構建一個 Web 服務器。 創建一個 `http.js` 文件 輸入以下代碼: ```javascript= // 引入 http 核心模塊 const http = require("http"); // http.createServer 方法創建服務器 // 它會返回一個服務器 所以要用一個變量 server 接收 const server = http.createServer(); // 使用 server.on 方法監聽 reqquest 事件 // 收到 request 請求的時候執行回調 log 收到請求 server.on("request", () => { console.log("收到請求。"); }); // 最後使用 listen 創建端口號 server.listen(5000, () => { console.log("running..."); }); ``` 開啟終端機執行 `node http.js` 出現 `running...` 後 開啟瀏覽器 進入網址 `127.0.0.1:5000/` 即可發送請求。 此時回到終端機就會收到 log 的收到請求。 ### 構建一個 Web 服務器的進階版 在發送請求後獲取請求的 url 路徑 接著響應一些文字 代碼如下: ```javascript= const http = require("http"); const server = http.createServer(); // 主要修改代碼 回調函數中傳入兩參數 // 一個是請求一個是響應 server.on("request", (req, res) => { // req.url 可以獲得 :5000 後的 url 地址 console.log(req.url); // res.write 方法可寫入文字 且會被瀏覽器編譯 所以可以寫入標籤 res.write("<h1>TEST RES</h1>"); // res.end 方法是結束響應 不然會一直轉圈圈 res.end() }); server.listen(5000, () => { console.log("running..."); }); ``` - 注意:在瀏覽器中沒有設置編碼的情況下 默認是使用當前操作系統的編碼 - 中文操作系統的編碼是 gbk 所以假設你 res.write 傳入中文 瀏覽器渲染結果會是中文亂碼 上述問題處理方式:手動設置 `content-type` 設置方法代碼如下: ```javascript= const http = require("http"); const server = http.createServer(); server.on("request", (req, res) => { console.log(req.url); // 主要添加這一行 res.setHeader("Content-Type", "text/plain; charset=utf-8"); // 接著傳入中文測試,即可發現不再是亂碼 res.write("我是中文字!"); res.end() }); server.listen(5000, () => { console.log("running..."); }); ``` - response.setHeader() 方法只能寫入單個響應頭設定 - response.writeHead() 方法可用物件方式傳入多個響應頭設定,包括狀態碼,內容和多個標頭等 >附上 [content-type 種類連結參考網址](https://www.runoob.com/http/http-content-type.html ) ### 發送不同格式的響應 上方提到的都是使用 res.write() 方法響應文字內容 接著說明針對不同響應格式的操作方式: #### 響應 HTML 格式 實例如下: ```javascript= var http = require("http"); var fs = require("fs"); http .createServer(function (req, res) { // 設置響應頭 res.writeHead(200, { "Content-Type": "text/html" }); // 使用核心模塊 fs 讀取任意的 .html 檔案並將讀取到的內容發送 fs.readFile("./index.html", (err, data) => { if (err) { return res.end("Something wrong!"); } res.end(data); }); }) .listen(5000, () => { console.log("Server running..."); }); ``` 可通過在終端機中輸入 `node 代碼文件名稱` 指令執行 並開啟本地端 輸入端口號 5000 測試實際顯示內容 #### 響應 JSON 格式 實例如下: ```javascript= var http = require("http"); var fs = require("fs"); http .createServer(function (req, res) { // 設置響應頭 res.writeHead(200, { "Content-Type": "application/json" }); // 手寫一段 json 做測試用 let json_response = { status: 200, message: "succssful", result: ["sunday", "monday", "tuesday", "wednesday"], code: 2000, }; // 把 json 轉為 string 並發送響應 res.end(JSON.stringify(json_response)); }) .listen(5000, () => { console.log("Server running..."); }); ``` 可通過在終端機中輸入 `node 代碼文件名稱` 指令執行 並開啟本地端 輸入端口號 5000 測試實際顯示內容 #### 響應 PDF 格式 實例如下: ```javascript= var http = require("http"); var fs = require("fs"); http .createServer(function (req, res) { // 設置響應頭 res.writeHead(200, { "Content-Type": "application/pdf" }); // 使用核心模塊 fs 讀取 .pdf 並將讀取到的內容發送 fs.readFile("test.pdf", (error, data) => { if (error) { return res.end("Something wrong!"); } else { res.write(data); res.end(); } }); }) .listen(5000, () => { console.log("Server running..."); }); ``` 可通過在終端機中輸入 `node 代碼文件名稱` 指令執行 並開啟本地端 輸入端口號 5000 測試實際顯示內容 #### 響應 MP3/MP4 格式 實例如下: ```javascript= var http = require("http"); var fs = require("fs"); http .createServer(function (req, res) { // 設置響應頭 mp4 設為 video/mp4 其餘都相同 res.writeHead(200, { "Content-Type": "audio/mp3" }); // exists 用於判斷文件是否存在 fs.exists( "https://www.learningcontainer.com/wp-content/uploads/2020/02/Kalimba.mp3", function (exists) { if (exists) { var rstream = fs.createReadStream( "https://www.learningcontainer.com/wp-content/uploads/2020/02/Kalimba.mp3" ); rstream.pipe(res); } else { res.end("Something wrong!"); } } ); }) .listen(5000, () => { console.log("Server running..."); }); ``` - createReadStream 用於把東西轉成數據流 - 可以通過 數據流.pipe(res) 方法 把數據流傳給響應 - pipe 是 Node.js 的方法 該方法可理解為是用於連接兩個東西的管道 可通過在終端機中輸入 `node 代碼文件名稱` 指令執行 並開啟本地端 輸入端口號 5000 測試實際顯示內容 ## require 是什麼 首先你要知道在 Node.js 中無法一次執行多個 js 腳本文件 你沒辦法通過命令 `node a.js b.js` 來同時執行 a.js 和 b.js 文件,它只會執行第一個(即 a.js 文件) 那若要做到同時執行 a.js 和 b.js 文件,就需要使用這個神奇的 require 來處理了 舉例來說,我們就可以在 a.js 中 require 那個也需要執行的 b.js 文件 代碼如下: ```javascript= // a.js 文件內容如下 console.log('a start') require('./b.js') // 這行就是引入 b.js 用的 console.log('a end') // b.js 文件內容如下 console.log('bbb') ``` 然後在終端機中執行命令 `node a.js` 輸出結果就是: 1. a start 2. bbb 3. a end 另外要注意的是 Node.js 中沒有全局作用域 只有模塊作用域(可理解為文件作用域) 即在 a.js 中定義的變量或函數 即使在 a.js 引入 b.js 也無法在 b.js 中調用到 a.js 所定義的東西 那同理, a.js 也無法使用到 b.js 定義的變量或函數,他們的作用域都只在自己的文件本身,這就是模塊作用域,a.js 跟 b.js 各自是一個模塊。 - 補充:`require` 可以省略後綴名(即省略`.js`) 因為它默認就會找 js 檔案 - `require` 的查找機制為: `.js`檔 > `.json`檔 > `.node`檔 ### exports 是什麼 首先要知道,在每個模塊中默認都有一個 `exports` 對象 每個模塊都可以通過 `exports` 對象來導出模塊成員 舉例代碼如下: ```javascript= // b.js 文件中的內容 // 我們直接用 exports.xxx 來導出這個 function exports.add = function(x,y){ return x + y } // a.js 文件中的內容 const b = require('./b.js') // 引入 b.js 文件為 b console.log(b.add(10,20)) // 通過 b.add 就可以調用到 b.js 文件中的該函式了 // 這個 add 就是在 b.js 中通過 exports 導出的名稱 add // 所以此時若執行 `node a.js` 就可以獲得 30 ``` - 默認 exports 是一個空對象,每個文件模塊中都有這個對象存在,單看你是否要使用。 ## 模板引擎的使用 首先介紹下 `art-template` 模板引擎,他在瀏覽器與後端皆可使用。 使用方法為: 1. 安裝 開啟終端機輸入 `npm i art-template` 2. 引入 `const template = require('art-template')` 3. 撰寫 - 撰寫方法代碼如下: ```javascript= const templaet = require('art-template') template.render('<h1>{{title}}</h1>',{ title : '我是標題' }) ``` 附上 [art-template 官網連結](https://aui.github.io/art-template/zh-cn/)參考。 .......待續