Node.js
2023-08-09 14:53:19 # Frontend # Node.js

Node.js

初识 Node.js 与内置模块

1. fs 文件系统模块

1.1 什么是 fs

fs 模块是 Node.js 官方提供的,用来操作文件的模块。提供了一系列的方法和属性,用来满足用户对文件的操作需求

  • fs.readFile(): 用来读取指定文件的内容
  • fs.writeFile(): 用来向指定的文件写入内容

如果要在 JS 代码中,使用 fs 模块来操作文件,需要使用如下方式导入

1
const fs = require('fs')

1.2 读取指定文件的内容

fs.readFile()的语法格式

1
fs.readFile(path[, options], callback)
  • 参数1: 必选参数,字符串,表示文件的路径
  • 参数2: 可选参数,表示以什么编码格式来读取文件
  • 参数3: 必选参数,文件读取完成后,通过回调函数拿到读取的结果

fs.readFile()的示例代码

1
2
3
4
5
6
// 以 UTF-8 的编码格式, 读取指定文件内容,并打印 err 和 dataStr 的值
const fs = require('fs');
fs.readFile('./11.txt', 'utf8', function(err, dataStr) {
console.log(err);
console.log(dataStr);
});
  • 读取成功

    • err: null
    • dataStr: 文件内容
  • 读取失败

    • err: 错误对象
    • dataStr: undefined

判断文件是否读取成功

可以判断 err 对象是否为 null

1
2
3
4
5
6
7
const fs = require('fs');
fs.readFile('./11.txt', 'utf8', function(err, dataStr) {
if(err) {
return console.log('文件读取失败: ' + err.message);
}
console.log('文件读取成功, 内容是: ' + dataStr);
});

1.3 向指定文件写入内容

fs.writeFile()的语法格式

1
fs.writeFile(file, data[, options], callback)
  • 参数1: 必选参数,需要制定一个文件路径的字符串
  • 参数2: 必选参数,表示要写入的内容
  • 参数3: 可选参数,表示以什么编码格式来写入文件内容, 默认值是 utf8
  • 参数4: 必选参数,文件写入完成后的回调函数

fs.writeFile()的示例代码

1
2
3
4
const fs = require('fs');
fs.writeFile('./11.txt', 'Hello Node.js', function(err) {
console.log(err);
});
  • 写入成功
    • err: null
  • 写入失败
    • err: 错误对象

判断文件是否写入成功

可以判断 err 对象是否为 null

1
2
3
4
5
6
7
const fs = require('fs');
fs.writeFile('./11.txt', 'Hello Node.js', function(err) {
if(err) {
return console.log('文件写入失败: ' + err.message);
}
console.log('文件写入成功');
});

1.4 处理路径问题

在使用 fs 模块操作文件时,如果提供的操作路径是以 ./ 或 ../ 开头的相对路径时,很容易出现路径动态拼接错误的问题

  • 原因:代码在运行时,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径
  • 解决:直接提供完整的路径
    • __dirname 表示当前文件所处目录
1
2
3
4
5
const fs = require('fs');
fs.readFile(__dirname + '/11.txt', 'utf8', function(err, dataStr) {
console.log(err);
console.log(dataStr);
});

2. path 路径模块

2.1 什么是 path 路径模块

path 模块是 Node.js 官方提供的,用来处理路径的模块。提供了一系列的方法和属性,用来满足用户对路径的处理需求

  • path.join(): 用来将多个路径片段拼接成一个完整的路径字符串
  • path.basename(): 用来从路径字符串中,将文件名解析出来

使用如下方法导入

1
const path = require('path')

2.2 路径拼接

path.join()的语法格式

1
path.join([...paths])
  • 参数: …paths 字符串类型,路径片段的序列
  • 返回值: 字符串

path.join()的代码示例

1
2
3
4
5
6
const path = require('path');
const pathStr = path.join('/a', '/b/c', '../', './d', 'e');
console.log(pathStr); // 输出 \a\b\d\e

const pathStr2 = path.join(__dirname, './11.txt');
console.log(pathStr2);
  • 今后涉及到路径的拼接操作,使用 path.join(),不要直接使用 + 进行字符串的拼接

2.3 获取路径中的文件名

path.basename()的语法格式

使用 path.basename() 可以获取到路径中的最后一部分,常通过该方法获取路径中的文件名

1
path.basename(path[, ext])
  • path: 必选参数,表示一个路径的字符串
  • ext: 可选参数,表示文件扩展名的字符串
  • 返回值: 路径中的最后一部分

path.basename()的代码示例

1
2
3
4
5
6
7
8
const path = require('path');

const fpath = '/a/b/c/index.html'
var fullName = path.basename(fpath)
console.log(fullName) // index.html

var nameWithoutExt = path.basename(fpath, '.html')
console.log(nameWithoutExt) //index

2.4 获取路径中的文件扩展名

path.extname()的语法格式

使用 path.extname() 可以获取到路径中的扩展名部分

1
path.extname(path)
  • path: 必选参数,表示一个路径的字符串
  • 返回值: 扩展名字符串

path.extname()的代码示例

1
2
3
4
const path = require('path');
const fpath = '/a/b/c/index.html'
const fext = path.extname(fpath)
console.log(fext) // .html

3. http 模块

3.1 什么是 http 模块

http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。通过 http 模块提供的 http.createServer() 方法,就能方便地把一台普通的电脑,变成一台 web 服务器,从而对外提供服务

使用如下方法导入

1
const http = require('http')

3.2 创建基本 Web 服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const http = require('http')

// 创建 web 服务器实例
const server = http.createServer()

// 为服务器实例绑定 request 事件,监听客户端的请求
server.on('request', function (req, res) {
// 只要有请求,就会触发 request 事件,从而调用这个事件处理函数
const url = req.url
const method = req.method
const str = `Your request url is ${url}, and request method is ${method}`
console.log(str)

// 设置 Content-Type 响应头,解决中文乱码的问题
res.setHeader('Content-Type', 'text/html; charset=utf-8')
// 向客户端响应内容
res.end(str)
})

// 调用 .listen() 方法就能启动服务器
server.listen(8080, function () {
console.log('server running at http://127.0.0.1:8080')
})

req 请求对象

只要服务器接收到了客户端的请求,就会调用通过 server.on() 为服务器绑定的 request 事件处理函数

如果想在事件处理函数中,访问与客户端相关的数据或属性,可以使用 req 对象

res 响应对象

在服务器的 request 事件处理函数中,访问与服务器相关的数据或属性,可以使用 req 对象

3.3 根据不同 url 响应不同内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const http = require('http')
const server = http.createServer()

server.on('request', (req, res) => {
const url = req.url
// 设置默认的响应内容为 404 Not found
let content = '<h1>404 Not found!</h1>'
// 判断用户请求的是否为 / 或 /index.html 首页
// 判断用户请求的是否为 /about.html 关于页面
if (url === '/' || url === '/index.html') {
content = '<h1>首页</h1>'
} else if (url === '/about.html') {
content = '<h1>关于页面</h1>'
}

res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(content)
})

server.listen(80, () => {
console.log('server running at http://127.0.0.1')
})

模块化

1. 模块化的基本概念

1.1 什么是模块化

  • 模块化是指解决一个复杂问题时,自顶向下逐层把系统划分为若干模块的过程,模块是可组合、分解和更换的单元。
  • 编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并相互依赖的多个小模块
  • 模块化可提高代码的复用性和可维护性,实现按需加载。

1.2 模块化规范

  • 模块化规范是对代码进行模块化拆分和组合时,需要遵守的规则
    • 如使用何种语法格式引用模块和向外暴露成员

2. Node.js 中的模块化

2.1 Node.js 中模块的分类

  • 内置模块:由 Node.js 官方提供的,例如 fs, path, http 等
  • 自定义模块:用户创建的每个 .js 文件,都是自定义模块
  • 第三方模块:由第三方开发出来的模块,使用前需下载

2.2 加载模块

  • require() 方法,可以加载三类模块,只有加载自定义模块需要路径名(可以省略后缀名)
    • 使用 require() 加载模块时,会执行被加载模块中的代码

2.3 模块作用域

  • 和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域

  • 可以防止全局变量污染

2.4 向外共享模块作用域中的成员

module 对象

  • 每个 .js 自定义模块中都有一个 module 对象,它里面存储了和当前模块有关的信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Module {
id: '.',
path: 'E:\\WorkSpace\\VSC\\nodejs',
exports: {},
filename: 'E:\\WorkSpace\\VSC\\nodejs\\a.js',
loaded: false,
children: [],
paths: [
'E:\\WorkSpace\\VSC\\nodejs\\node_modules',
'E:\\WorkSpace\\VSC\\node_modules',
'E:\\WorkSpace\\node_modules',
'E:\\node_modules'
]
}

module.exports 对象

  • 在自定义模块中,可以使用 module.exports 对象,将模块内的成员共享出去,供外界使用。
  • 外界使用 require() 导入自定义模块时,得到的就是 module.exports 指向的对象。
  • 使用 require() 导入模块时,导入的结果永远以 module.exports 指向的对象为准
1
2
3
4
5
// 向 module.exports 对象上挂载属性和方法
module.exports.username = 'zs';
module.exports.sayHello = function() {
console.log('Hello!');
};

exports 对象

  • 默认情况下,exportsmodule.exports 指向同一个对象。最终共享的结果,以 module.exports 指向的对象为准。

2.5 Node.js 中的模块化规范

Node.js 遵循了 CommonJS 模块化规范,CommonJS 规定了模块的特性和各模块之间如何互相依赖

CommonJS 模块化规范

  • 每个模块内部,module 变量代表当前模块
  • module 变量是一个对象,module.exports 是对外的接口
  • 加载某个模块即加载该模块的 module.exports 属性,require() 用于加载模块

3. npm 与包

3.1 包

  • Node.js 中的第三方模块又叫做包,由第三方个人或团队开发出来,免费供所有人使用
  • 包是基于内置模块封装出来的

3.2 npm

初次装包后多了哪些文件

  • node_modules 文件夹
    • 用来存放所有已安装到项目中的包
  • package-lock.json 配置文件
    • 记录 node_modules 目录下的每一个包的下载信息,例如包名、版本号、下载地址
  • 不需要手动修改,npm 会自动维护

安装指定版本的包

  • 默认安装最新版本的包,可以通过 包名@版本号 指定具体版本

包的语义化版本规范

分为三位数字,以”点分十进制”形式进行定义, eg. 2.24.0

  • 第 1 位: 大版本
  • 第 2 位: 功能版本
  • 第 3 位: Bug修复版本

3.3 包管理配置文件

npm 规定,在项目根目录中,必须提供一个叫做 package.json 的包管理配置文件。用来记录与项目有关的一些配置信息,例如

  • 项目的名称、版本号、描述等
  • 项目中都用到了哪些包
  • 哪些包只在开发期间会用到
  • 哪些包在开发和部署时都需要用到

快速创建 package.json

1
npm init -y

安装包

使用 npm i(nstall) 可以一次性安装所有的依赖包

  • 会先读取 package.json 中的 dependencies 节点

卸载包

npm uninstall

devDependencies 节点

如果某些包只在项目开发阶段用到,项目上线后不会用到,则建议把这些包记录到 devDependencies 节点

1
2
3
4
# 记录到 devDependencies 节点
npm i 包名 -D
# 上面是简写,等价于下方完整写法
npm install 包名 --save-dev

与之相对,如果某些包在开发和上线都需要用到,则建议把这些包记录到 dependencies 节点

3.4 npm 换源

1
2
3
4
# 查看当前下载源
npm config get registry
# 切换为淘宝源
npm config set registry=http://registry.npmmirror.com

4. 模块的加载机制

4.1 优先从缓存中加载

模块第一次加载后会被缓存,即多次调用 require() 不会导致模块的代码被执行多次,提高模块加载效率

4.2 内置模块的加载机制

内置模块加载优先级最高。

4.3 自定义模块的加载机制

加载自定义模块时,路径要以 ./../ 开头,否则会作为内置模块或第三方模块加载。

导入自定义模块时,若省略文件扩展名,则 Node.js 会按顺序尝试加载文件:

  • 按确切的文件名加载
  • 补全 .js 扩展名加载
  • 补全 .json 扩展名加载
  • 补全 .node 扩展名加载
  • 报错

4.4 第三方模块加载

  • 若导入第三方模块,Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块。
  • 如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录

例如,假设在 C:\Users\bruce\project\foo.js 文件里调用了 require('tools'),则 Node.js 会按以下顺序查找:

  • C:\Users\bruce\project\node_modules\tools
  • C:\Users\bruce\node_modules\tools
  • C:\Users\node_modules\tools
  • C:\node_modules\tools

4.5 目录作为模块加载

当把目录作为模块标识符进行加载的时候,有三种加载方式:

  • 在被加载的目录下查找 package.json 的文件,并寻找 main 属性,作为 require() 加载的入口
  • 如果没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件。
  • 若失败则报错

Express

1. 初识 Express

官网传送门

基于 Node.js 平台,快速、开放、极简的 Web 开发框架

Express 是用于快速创建服务器的第三方模块

1.1 基本使用

安装 Express:

1
npm install express

创建服务器,监听客户端请求,并返回内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const express = require('express')
// 创建 web 服务器
const app = express()

// 监听客户端的 GET 和 POST 请求,并向客户端响应具体的内容
app.get('/user', (req, res) => {
res.send({ name: 'zs', age: 20, gender: '男' })
})
app.post('/user', (req, res) => {
res.send('请求成功')
})

app.get('/', (req, res) => {
// 通过 req.query 可以获取到客户端发送过来的查询参数
// ?name=zs&age=20
// req.query.name req.query.age
console.log(req.query)
res.send(req.query)
})

// 这里的 :id 是一个动态的参数
app.get('/user/:id/:username', (req, res) => {
// req.params 是动态匹配到的 URL 参数,默认是一个空对象
console.log(req.params)
res.send(req.params)
})

app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})

1.2 托管静态资源

  • 通过 express.static() 方法可创建静态资源服务器,向外开放访问静态资源。
  • Express 在指定的静态目录中查找文件,并对外提供资源的访问路径,存放静态文件的目录名不会出现在 URL 中
  • 访问静态资源时,会根据目录的添加顺序查找文件
  • 可为静态资源访问路径添加前缀
1
2
3
4
5
6
7
8
9
10
11
12
13
app.use(express.static('public'))
app.use(express.static('files'))
app.use('/bruce', express.static('bruce'))

/*
可直接访问 public, files 目录下的静态资源
http://localhost:3000/images/bg.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/login.js

通过带有 /bruce 前缀的地址访问 bruce 目录下的文件
http://localhost:8080/bruce/images/logo.png
*/

2. Express 路由

2.1 路由的概念

  • Express 中,路由指的是客户端的请求与服务器处理函数之间的映射关系
  • Express 中的路由分 3 部分组成,分别是请求的类型、请求的 URL 地址、处理函数
1
app.METHOD(PATH, HANDLER)

2.2 路由的使用

  • 简单使用: 把路由直接挂载到 app 上,如上述代码所示

  • 模块化路由: 为了方便对路由进行模块化的管理,Express不建议将路由直接挂载到 app 上,而是推荐将路由抽离为单独的模块。将路由抽离为单独模块的步骤如下:

    1. 创建路由模块对应的 .js 文件
    2. 调用 express.Router() 函数创建路由对象
    3. 向路由对象上挂载具体的路由
    4. 使用 module.exports 向外共享路由对象
    5. 使用 app.use() 函数注册路由模块
  • 创建路由模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // router.js
    const express = require('express')
    // 创建路由对象
    const router = express.Router()

    // 挂载具体路由
    router.get('/user/list', (req, res) => {
    res.send('Get user list.')
    })
    router.post('/user/add', (req, res) => {
    res.send('Add new user.')
    })

    // 向外导出路由对象
    module.exports = router
  • 注册路由模块,添加访问前缀

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const express = require('express')
    const router = require('./router')

    const app = express()

    // 注册路由模块,添加访问前缀(可选)
    app.use('/api', router)

    app.listen(80, () => {
    console.log('http://127.0.0.1')
    })
  • app.use() 函数的作用,就是来注册全局中间件

3. Express 中间件

3.1 中间件的概念

  • 中间件(Middleware),特指业务流程的中间处理环节