---
# System prepended metadata

title: Node.js
tags: [Node.js]

---

---
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/)參考。




.......待續