# Node.js

> *Node*.*js* 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。*Node*.*js* 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。

## 1. 基础知识

在 NodeJS 中，一般将代码合理拆分到不同的 JS 文件中，每一个文件就是一个模块，而文件路径就是模块名。在编写每个模块时，都有 require、exports、module 三个预先定义好的变量可供使用（CommonJS 规范）。

```javascript
// require - 在当前模块中加载和使用别的模块，传入一个模块名，返回一个模块导出对象。
var foo1 = require('./foo');
var foo2 = require('./foo.js');
```

```javascript
// exports - 当前模块的导出对象，用于导出模块公有方法和属性。
// module - 通过 module 对象可以访问到当前模块的一些相关信息，但最多的用途是替换当前模块的导出对象。
const newModule = {
	add: (a, b) => a + b
}
module.exports  = newModule;
```

## 2. 网络模块

### 2.1 Http 模块

```javascript
const http = require('http')
const server = http.createServer(function (req, res) {
    res.writeHead(200,{"Content-Type":"text/html;charset=UTF8"}); //插入响应头
    if (req.url === '/index.html') {
        res.write('This is demo.')
    } else {
        res.write('404')
    }
    res.end();
});

server.listen(3000, function () {
    console.log('服务器启动成功了，可以通过 http://127.0.0.1:3000/ 来进行访问')
});
```

### 2.2 URL 模块（解析 URL）

```javascript
let jd_url = "https://search.jd.com/Search?keyword=空气净化器&enc=utf-8";
let { pathname, query } = url.parse(request.url, true); // true 表示同时解析 query 字段
/*
query: { 
keyword: '空气净化器',
enc: 'utf-8'
}
pathname: '/Search'
*/
```

### 2.3 多进程

```javascript
const os = require("os");
const http = require("http");
const process = require("process");
const cluster = require("cluster");

// 主进程：分配任务
if (cluster.isMaster) {
    console.log(`主进程PID：${process.pid}`);
    for (let i = 0; i < os.cpus().length; i++) {
        cluster.fork();
    }
} else {
    // 子进程执行任务
    http.createServer((request, response) => {
        console.log(`当前进程PID：${process.pid}，父进程PPID：${process.ppid}`);
        response.write("Fork Test");
        response.end();
    }).listen(8080, () => {
        console.log(`服务器启动成功，当前端口：8080，进程PID：${process.pid}`);
    });
}
```

```
结果：
主进程PID：12316
服务器启动成功，当前端口：8080，进程PID：20700
服务器启动成功，当前端口：8080，进程PID：22108
服务器启动成功，当前端口：8080，进程PID：20052
服务器启动成功，当前端口：8080，进程PID：20720

访问结果（只有进程忙不过来才会让其他进程处理，否则都是该进程处理）：
当前进程PID：20720，父进程PPID：12316
当前进程PID：20720，父进程PPID：12316
当前进程PID：20720，父进程PPID：12316
当前进程PID：20720，父进程PPID：12316
```

## 3. 文件模块

### 3.1 fs 模块

* 检测是文件还是目录 - fs.stat
* 创建目录 - fs.mkdir
* 写文件 - fs.writeFile
* 读文件 - fs.readFile
  * toString()：buffer转换成字符串
  * toJSON()：buffer转化成Json
* 追加文件 - fs.appendFile (如果没有文件则会生成文件，相当于创建文件)
* 读取目录 - fs.readdir
* 重命名 - fs.rename
* 删除目录 - fs.rmdir
* 删除文件 - fs.unlink
* 管道流（大文件一般都是用流）

```javascript
// 创建一个可读流
const readerStream = fs.createReadStream('demo.txt');
// 创建一个可写流
const writerStream = fs.createWriteStream('output.txt');
// 管道读写操作 - 读取 input.txt 文件内容，并将内容写入到 output.txt 文件中
readerStream.pipe(writerStream);
```

### 3.2 zlib 模块（压缩包）

```javascript
const fs = require('fs')
const zlib = require('zlib')

const readerStream = fs.createReadStream('demo.txt');
const gz = zlib.createGzip();
const writerStream = fs.createWriteStream('demo.txt.gz');

// readerStream水龙头先传给了gz，gz又当一个水龙头传给了writerStream
readerStream.pipe(gz).pipe(writerStream);

writerStream.on("finish", () => {
    console.log("写入完毕");
});
```

## 4. Express 框架

### 4.1 快速入门

安装

```
$ npm install express --save
```

生成器工具 `express-generator` 快速创建一个应用的骨架

```
$ npx express-generator
```

路由

```javascript
const express = require('express')
const app = express()
app.listen(3000, () => console.log(`Example app listening on port ${port}!`))

// 格式 - app.METHOD(PATH, HANDLER)
// app为express实例，path为路径，HANDLER为路由匹配时执行的操作
app.get('/', (req, res) => res.send('Hello World!'))
app.post('/', (req, res) => res.send('Got a POST request'))
app.put('/user', (req, res) => res.send('Got a PUT request at /user'))
app.delete('/user', (req, res) => res.send('Got a DELETE request at /user'))
```

静态文件读取

```javascript
// 图像、CSS 文件和 JavaScript 文件之类的静态文件，使用 Express 中的 express.static 内置中间件函数

// 访问public目录中的所有文件，例如http://localhost:3000/images/pic.jpg等
app.use(express.static('public'))
// 通过带有/static前缀地址来访问public目录中的文件，例如http://localhost:3000/static/images/pic.jpg
app.use('/static', express.static('public'))
```

### 4.2 路由

格式： `app.METHOD(path, [callback...], callback)`

```javascript
// 1、Path路径匹配
// 字符串
app.get('/', (req, res) => {res.send('root')}); // 匹配根路径的请求
app.get('/about', (req, res) => {res.send('about')}); // 匹配 /about 路径的请求
// 字符串模式
app.get('/ab+cd', (req, res) => {res.send('ab+cd')}; // 匹配 abcd、abbcd、abbbcd等
// 正则表达式
app.get(/a/, (req, res) => {res.send('/a/')}; // 匹配任何路径中含有 a 的路径
```

```javascript
// 2、callback路由句柄 - 可以利用next请求处理提供多个回调函数
// 使用多个回调函数处理路由
app.get('/example/b', function (req, res, next) {
  console.log('response will be sent by the next function ...');
  next();
}, function (req, res) {
  res.send('Hello from B!');
});
// 使用回调函数数组处理路由
const callback_a = function (req, res, next) {
  console.log('response will be sent by the next function ...');
  next();
}
const callback_b = function (req, res) {
  res.send('Hello from B!');
}
app.get('/example/b', [callback_a,callback_b]);
```

```javascript
// 注意一个特殊的请求all，它的作用是对于一个路径上的所有请求加载中间件：
// 所有来自 “/secret” 的请求，不管使用 GET、POST、PUT、DELETE 或其他任何 http 模块支持的 HTTP 请求，句柄都会得到执行。
app.all('/secret', function (req, res, next) {
  console.log('Accessing the secret section ...');
  next(); // pass control to the next handler
});
```

响应对象（`res`）的方法向客户端返回响应：

| 方法               | 描述                             |
| ---------------- | ------------------------------ |
| res.download()   | 提示下载文件。                        |
| res.end()        | 终结响应处理流程。                      |
| res.json()       | 发送一个 JSON 格式的响应。               |
| res.jsonp()      | 发送一个支持 JSONP 的 JSON 格式的响应。     |
| res.redirect()   | 重定向请求。                         |
| res.render()     | 渲染视图模板。                        |
| res.send()       | 发送各种类型的响应。                     |
| res.sendFile()   | 以八位字节流的形式发送文件。                 |
| res.sendStatus() | 设置响应状态代码，并将其以字符串形式作为响应体的一部分发送。 |

链式路由：

```javascript
// 所有来自'/book'的请求都写在一起，有利于模块化路由
app.route('/book').get(function(req, res) {
  res.send('Get a random book');
}).post(function(req, res) {
  res.send('Add a book');
}).put(function(req, res) {
  res.send('Update the book');
});
```

模块化路由的写法

```javascript
// 路由文件 newRouter.js
var express = require('express');
var router = express.Router();
// 该路由使用的中间件
router.use(function timeLog(req, res, next) {
  console.log('Time: ', Date.now());
  next();
});
// 定义网站主页的路由
router.get('/', function(req, res) {
  res.send('Birds home page');
});
// 定义 about 页面的路由
router.get('/about', function(req, res) {
  res.send('About birds');
});
module.exports = router

// 使用文件 index.js
const newRouter = require('/newRouter.js');
app.use('/newRouter', newRouter)
// 可自动处理/newRouter 和 /newRouter/about 路径的请求
```

### 4.3 中间件Middleware

Express 本身就是由路由和中间件构成一个的 web 开发框架，中间件可以访问请求对象、响应对象和next变量，(不知道是否可以简单理解为回调函数)，其功能包括功能包括：

* 执行任何代码。
* 修改请求和响应对象。
* 终结请求-响应循环。
* 调用堆栈中的下一个中间件。

应用级中间件：绑定到 app 对象使用 `app.use()` 和 `app.METHOD()`：

```javascript
var app = express();

// 没有挂载路径的中间件，应用的每个请求都会执行该中间件
app.use(function (req, res, next) {
  console.log('Time:', Date.now());
  next();
});

// 挂载至 /user/:id 的中间件，任何指向 /user/:id 的请求都会执行它
app.use('/user/:id', function (req, res, next) {
  console.log('Request Type:', req.method);
  next();
});

// 路由和句柄函数(中间件系统)，处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
  res.send('USER');
});
```

路由级中间件：绑定的对象为 `express.Router()`：

```javascript
var app = express();
var router = express.Router();

// 没有挂载路径的中间件，通过该路由的每个请求都会执行该中间件
router.use(function (req, res, next) {
  console.log('Time:', Date.now());
  next();
});

// 一个中间件栈，显示任何指向 /user/:id 的 HTTP 请求的信息
router.use('/user/:id', function(req, res, next) {
  console.log('Request URL:', req.originalUrl);
  next();
}, function (req, res, next) {
  console.log('Request Type:', req.method);
  next();
});

// 一个中间件栈，处理指向 /user/:id 的 GET 请求
router.get('/user/:id', function (req, res, next) {
  // 如果 user id 为 0, 跳到下一个路由
  if (req.params.id == 0) next('route');
  // 负责将控制权交给栈中下一个中间件
  else next(); //
}, function (req, res, next) {
  // 渲染常规页面
  res.render('regular');
});

// 处理 /user/:id， 渲染一个特殊页面
router.get('/user/:id', function (req, res, next) {
  console.log(req.params.id);
  res.render('special');
});
```

错误处理中间件：使用 4 个参数(err, req, res, next)：

```javascript
app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});
```

第三方中间件（以解析 cookie 的中间件 cookie-parser 为例）：

```javascript
// $ npm install cookie-parser
var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');

// 加载用于解析 cookie 的中间件
app.use(cookieParser());
```

{% hint style="info" %}
参考资料：

* [一文入门 NodeJS](https://www.cnblogs.com/dotnetcrazy/p/10118756.html)
* [七天学会 NodeJS](http://nqdeng.github.io/7-days-nodejs/)
* [NodeJS 教程](https://www.runoob.com/nodejs/nodejs-tutorial.html)
* [Express 开发框架](http://caibaojian.com/expressjs/index.html)
* [Node 入门](https://www.nodebeginner.org/index-zh-cn.html#javascript-and-nodejs)
  {% endhint %}

{% hint style="info" %}
如果你对内容有任何疑问，欢迎提交 [❕issues](https://github.com/MrEnvision/Front-end_learning_notes/issues) 或 [✉️ email](mailto:EnvisionShen@gmail.com)
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://sherwinshen.gitbook.io/qian-duan-xue-xi/network/node.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
