Node.js
一、Node.js 简介
- 什么是 Node.js?
- Node.js是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
- Node.js是一个开源和跨平台的 JavaScript 运行时环境。
- Node.js 中的 JavaScript 运行环境
- 内置 API -> 待执行的 JavaScript 代码 -> V8 引擎
- Node.js 要学什么
- JavaScript 基础语法
- Node.js 内置 API 模块(fs、path、http 等)
- 第三方 API 模块(express、mysql 等)
- Node.js 与浏览器的区别
- Node.js 没有浏览器提供的 document 、window 和所有其他对象。没有那些浏览器的 API。
- Node.js 通过其模块提供所有友好的 API。
- Node.js 可以控制环境。控制模块的版本。编写的 Node.js 版本支持所有现代的 ES6-7-8-9 JavaScript
- Node.js 使用 CommonJS模块系统。而浏览器使用的是 ES Modules标准。
- Node.js 中使用 require() ,在浏览器中使用 import
- JavaScript 引擎
- V8 引擎:V8 是驱动 Google Chrome 的 JavaScript 引擎的名称。这是在使用 Chrome 浏览时获取我们的 JavaScript 并执行它的东西。
- SpiderMonkey (OdinMonkey)引擎:Firefox。
- JavaScriptCore(Nitro)引擎:Safari。
- Chakra 引擎:Edge。
二、Node.js 环境的安装
- 进入 Node.js 的官网首页(https://nodejs.org/en/)
- 一直 next 安装就好
- 查一下已安装的 Node.js 的版本号 命令行输入:node -v
三、Node.js 基本使用
1. 从命令行运行 Node.js 脚本
node app.js
2. 如何退出 Node.js 程序
ctrl + c
process.exit(1)
process.exitCode = 1
process.kill(process.pid,'SIGTERM')
注:process 是不需要 require ,这个是自动可用的。
3. Node.js 从命令行接受参数
传参数
node app.js joe
%
访问用:const args = require('minimist')(process.argv.slice(2))
args['name']
%
%或者%
node app.js name=joe
%
访问用:const args = process.argv.slice(2)
args[0]
%
接收数据
- 通过使用循环迭代所有的参数(包括 node 路径和文件路径):
process.argv.forEach((val, index) => {
console.log(`${index}: ${val}`)
})
- 通过创建一个排除了前两个参数的新数组来仅获取其他的参数(前两个参数是 node 路径和文件路径):
const args = process.argv.slice(2)
4. 使用 node.js 输出到命令行
Node.js 提供了 console 模块,该模块提供了大量非常有用的与命令行交互的方法。
-
使用控制台模块的基础输出
Node.js 提供了 console 模块,该模块提供了大量非常有用的与命令行交互的方法。 -
清空控制台
console.clean() -
元素计数
console.count() -
打印堆栈踪迹
console.trace() const function2 = () => console.trace() const function1 = () => function2() function1()这会打印堆栈踪迹。运行后会打印以下内容:
Trace at function2 (repl:1:33) at function1 (repl:1:25) at repl:1:1 at ContextifyScript.Script.runInThisContext (vm.js:44:33) at REPLServer.defaultEval (repl.js:239:29) at bound (domain.js:301:14) at REPLServer.runBound [as eval] (domain.js:314:12) at REPLServer.onLine (repl.js:440:10) at emitOne (events.js:120:20) at REPLServer.emit (events.js:210:7) -
计算耗时
const doSomething = () => console.log('测试') const measureDoingSomething = () => { console.time('doSomething()') //做点事,并测量所需的时间。 doSomething() console.timeEnd('doSomething()') } measureDoingSomething() -
stdout 和 stderr
console.log 非常适合在控制台中打印消息。这就是所谓的标准输出(或称为 stdout)
console.log 会打印到 stderr 流。不会出席那在控制台中,但是会出现在错误日志中。
-
为输出着色
可以使用转译序列在控制台中为文本的输出着色。转义序列是一组标识颜色的字符。
console.log('\x1b[33m%s\x1b[0m', '你好')还有可以用 Chalk 这样一个库。需要安装 npm install chalk
const chalk = require('chalk') console.log(chalk.yellow('你好')) -
创建进度条
使用 Progress 是一个很棒的包。可以在控制台输出进度条。需要安装 npm install progress
const ProgressBar = require('progress') const bar = new ProgressBar(':bar', { total: 10 }) const timer = setInterval(() => { bar.tick() if (bar.complete) { clearInterval(timer) } }, 100)
5. 在 Node.js 中从命令行接收输入
使 Node.js CLI程序具有交互性。使用 readline模块进行执行。每次一行的从可读流(例如 process.stdin 流,在 Node.js程序执行期间该流就是终端输入)获取输入。
const readline = require('readline').createInterface({
input:process.stdin,
output:process.stdout
})
readline.question(`你叫什么名字?`, name => {
console.log(`你好 ${name}!`)
readline.close()
})
6. 使用 exports 从 Node.js 文件中公开功能
Node.js 文件可以导入其他 Node.js文件公开的功能。
导入别的 Node.js 方法:
const library = require('./library')
导入之前需要先导出。这也就是 module 提供的。module.exports API 可以做的事情。
const car = {
brand: 'Ford',
model: 'Fiesta'
}
module.exports = car
//在另一个文件中
const car = require('./car')
或者直接使用 exports 进行导出。
const car = {
brand: 'Ford',
model: 'Fiesta'
}
exports.car = car
exports.car = {
brand: 'Ford',
model: 'Fiesta'
}
module.expors 和 expors 都指向同一个对象。最终共享结果还是以 module.expors 指向的对象为准。
**注意:**require() 模块时,得到的永远是 module.exports 指向的对象。
7. npm 包管理器简介
-
npm 简介
npm 是 Node.js 标准的软件包管理器。
-
下载
npm 可以管理项目依赖的下载。
-
安装所有依赖
根据项目已经有的 package.json 文件。下载所有依赖。
npm install就会在 node_modules 文件夹(如果尚不存在则会创建)中安装项目所需的所有东西。
-
安装单个软件包
npm install <package-name>在这些命令中。我们会看到一些表示:
- –save 安装并添加条目到 package.json 文件的 dependencies。
- –save-dev 安装并添加条目到 package.json 文件的 devDependencies。
区别在于。devDependencies 通常是开发的工具(例如测试的库),而 dependencies 则是与生产环境中的应用程序相关。
-
更新软件包
更新所有软件包
npm update更新单个软件包
npm update <package-name> -
下载指定版本的软件包
npm install <package-name>@<版本号>
8. npm 将软件包安装到那里
当使用 npm 安装软件包的时候。分两种情况:本地安装 全局安装。
-
默认执行 npm install 命令的时候。
npm install lodash软件包会被安装到当前文件树种的 node_modules 子文件夹下。此时会自动给当前文件夹中存在的 package.json 文件的 dependencies 属性中添加这一个lodash名的条目。
-
当使用 -g 的时候。
npm install -g lodash这种情况,npm 不会将软件包安装到本地文件夹下,而是使用全局的位置。
全局的位置是在:C:\Users\YOU\AppData\Roaming\npm\node_modules
9. 如何使用或执行 npm 安装的软件包
-
先进行安装
npm install lodash -
之后进行导入
const lodash = require('lodash')
10. package.json 指南
package.json 文件是项目的清单。这个是工具的配置中心。你可以看到已经安装的软件包和其版本。
-
文件结构
{}很明显是个对象。文件的内容没有固定的要求。唯一的要求是必须要遵守 JSON 格式,否则,尝试以编程的方式进行对其属性进行访问。是访问不到的。
{ "name": "test-project", "version": "1.0.0", "description": "A Vue.js project", "main": "src/main.js", "private": true, "scripts": { "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run dev", "unit": "jest --config test/unit/jest.conf.js --coverage", "test": "npm run unit", "lint": "eslint --ext .js,.vue src test/unit", "build": "node build/build.js" }, "dependencies": { "vue": "^2.5.2" }, "devDependencies": { "autoprefixer": "^7.1.2", "babel-core": "^6.22.1", "babel-eslint": "^8.2.1", "babel-helper-vue-jsx-merge-props": "^2.0.3", "babel-jest": "^21.0.2", "babel-loader": "^7.1.1", "babel-plugin-dynamic-import-node": "^1.2.0", "babel-plugin-syntax-jsx": "^6.18.0", "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", "babel-plugin-transform-runtime": "^6.22.0", "babel-plugin-transform-vue-jsx": "^3.5.0", "babel-preset-env": "^1.3.2", "babel-preset-stage-2": "^6.22.0", "chalk": "^2.0.1", "copy-webpack-plugin": "^4.0.1", "css-loader": "^0.28.0", "eslint": "^4.15.0", "eslint-config-airbnb-base": "^11.3.0", "eslint-friendly-formatter": "^3.0.0", "eslint-import-resolver-webpack": "^0.8.3", "eslint-loader": "^1.7.1", "eslint-plugin-import": "^2.7.0", "eslint-plugin-vue": "^4.0.0", "extract-text-webpack-plugin": "^3.0.0", "file-loader": "^1.1.4", "friendly-errors-webpack-plugin": "^1.6.1", "html-webpack-plugin": "^2.30.1", "jest": "^22.0.4", "jest-serializer-vue": "^0.3.0", "node-notifier": "^5.1.2", "optimize-css-assets-webpack-plugin": "^3.2.0", "ora": "^1.2.0", "portfinder": "^1.0.13", "postcss-import": "^11.0.0", "postcss-loader": "^2.0.8", "postcss-url": "^7.2.1", "rimraf": "^2.6.0", "semver": "^5.3.0", "shelljs": "^0.7.6", "uglifyjs-webpack-plugin": "^1.1.1", "url-loader": "^0.5.8", "vue-jest": "^1.0.2", "vue-loader": "^13.3.0", "vue-style-loader": "^3.0.1", "vue-template-compiler": "^2.5.2", "webpack": "^3.6.0", "webpack-bundle-analyzer": "^2.9.0", "webpack-dev-server": "^2.9.1", "webpack-merge": "^4.1.0" }, "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" }, "browserslist": ["> 1%", "last 2 versions", "not ie <= 8"] } -
常用的属性
name:用于告知应用程序或软件包的名称
version:表明了当前的版本
description:是应用程序或软件包的简短描述
main:设置的是应用程序的入口
private:如果设置为true,则可以防止应用程序或软件包被意外地发布到 npm
scripts:定义了一组可以运行的 node 脚本
dependencies:设置了作为依赖安装的 npm 软件包的列表
devDependencies:设置了作为开发依赖安装的 npm 软件包的列表
engines:设置了此软件包/应用程序在哪个版本的 Node.js 上运行
browserslist:用于告知要支持哪些浏览器(及其版本)
author:列出软件包的作者名称
contributors:该项目可以有一个或多个贡献者。这里要把名字存在数组里
bugs:链接到软件包的问题跟踪器,最常用的是 GitHub 的 issues 页面
homepage:设置软件包的主页
license:指定软件包的许可证
keyword:此属性包含于软件包功能相关的关键字数组。用于让其他人搜索用
-
如果是下载的项目。其中没有 node_modules 依赖包。就需要安装依赖包。在命令行中用下面的语句。
npm install这样就会根据 package.json 的内容。进行对软件包的下载。
11. 使用 npm 的语义版本控制
-
语义版本控制的概念很简单:所有的版本都有三个数字:x.y.z
- x 是主版本
- y 是次版本
- z 是补丁版本
-
当发布新的版本时,不仅仅是随心所欲地增加数字,还要遵循以下规则:
- 当进行不兼容的 API 更改时,则升级主版本
- 当以向后兼容的方式添加功能时,则升级次版本
- 当进行向后兼容的缺陷修复时,则升级补丁版本
-
还有一些规则使用了下面这些符号:
^
~
>
>=
<
<=
=
||
-
这些符号规则解释如下:
^: 只会执行不更改最左边非零数字的更新。 如果写入的是^0.13.0,则当运行npm update时,可以更新到0.13.1、0.13.2等,但不能更新到0.14.0或更高版本。 如果写入的是^1.13.0,则当运行npm update时,可以更新到1.13.1、1.14.0等,但不能更新到2.0.0或更高版本。~: 如果写入的是〜0.13.0,则当运行npm update时,会更新到补丁版本:即0.13.1可以,但0.14.0不可以。>: 接受高于指定版本的任何版本。>=: 接受等于或高于指定版本的任何版本。<=: 接受等于或低于指定版本的任何版本。<: 接受低于指定版本的任何版本。=: 接受确切的版本。-: 接受一定范围的版本。例如:2.1.0 - 2.6.2。||: 组合集合。例如< 2.1 || > 2.6。
12. 卸载 npm 软件包
-
若要卸载之前在本地安装的软件包,也就是从node_modules 文件中卸载的话。需要运行:
npm uninstall <package-name> -
使用 -S 或 --save 标志,则此操作还会移除 package.json 文件中的引用
npm uninstall -S <package-name> -
如果软件包是开发依赖项(列出在
package.json文件中的devDependencies中),则必须使用 -D 或 --save-dev 标志从文件中移除npm uninstall -D <package-name> -
如果需要卸载全局安装的软件包,则需要添加 -g 或 --global 标志:
npm uninstall -g <package-name>
13. npm 依赖与开发依赖
-
依赖
使用
npm install <package-name>安装的 npm 软件包时,说将其安装为依赖项。此时会自动在 package.json 文件中的dependencies列表下列出。 -
开发依赖
使用
npm install -D <package-name>此时会将其安装为开发依赖项。会被添加到 package.json 文件中的devDependencies列表中列出。 -
区别
开发依赖仅仅实在开发中用的程序包,在生产环境中并不需要。
当投入生产环境时,如果输入
npm install且该文件夹包含 package.json 文件时,则会安装它们,因为 npm 会假定这是开发部署。需要设置
--production标志 。npm install --production ,以避免安装这些开发依赖项。
14. Node.js 事件循环
-
介绍
事件循环阐明了 Node.js 如何做到异步且具有非阻塞的 I/O。JavaScript 代码运行在单个线程上。每次只处理一件事。
在编写代码的时候只需要注意代码会在单个事件循环上运行,就可以避免任何可能阻塞线程的事情。例如同步的网络调用或无限的循环。
-
阻塞事件循环
任何花费太长时间才能将控制权返回给事件循环的 JavaScript 代码,都会阻塞页面中任何 JavaScript 代码的执行,甚至阻塞 UI 线程,并且用户无法单击浏览、滚动页面等。
JavaScript 中几乎所有的 I/O 基元都是非阻塞的。 网络请求、文件系统操作等。 被阻塞是个异常,这就是 JavaScript 如此之多基于回调(最近越来越多基于 promise 和 async/await)的原因。
-
调用堆栈
调用堆栈是一个 LIFO 队列(后进先出)
事件循环不断地检查调用堆栈,以查看是否需要运行任何函数。
当执行时,它会将找到的所有函数调用添加到调用堆栈中,并按顺序执行每个函数。
-
一个简单的事件循环的阐释
const bar = () => console.log('bar') const baz = () => console.log('baz') const foo = () => { console.log('foo') bar() baz() } foo() /* 结果为: foo bar baz */调用堆栈如下所示:

-
入队函数执行
上面的例子看起来就是顺序执行。下面我们看游戏啊如何将函数推迟直到堆栈被清空。
setTimeout( () => {}, 0) 的用例是调用一个函数,但是是在代码中的每个其他函数已被执行之后。
举个例子:
const bar = () => console.log('bar') const baz = () => console.log('baz') const foo = () => { console.log('foo') setTimeout(bar, 0) baz() } foo /* 输出结果: foo baz bar */调用堆栈如下所示:

是不是觉得很怪?
-
消息队列
用户触发的事件、setTimeout() 定时器等都会被放到消息队列中。
事件循环会赋予调用堆栈优先级,它首先处理在调用堆栈中找到的所有东西,一旦其中没有任何东西,便开始处理消息队列中的东西。
-
ES6 作业队列
ES6 中引入了作业队列的概念,Promise 使用了该队列。这种方式会尽快地执行异步函数的结果,而不是放在调用堆栈的末尾。
有个很好的比喻:消息队列把你排在了队尾。你只能等所有人都排完才到你。作业队列则是快速通道票,这样你就可以在完成上一次作业后立即乘坐另一趟车。
例子:
const bar = () => console.log('bar') const baz = () => console.log('baz') const foo = () => { console.log('foo') setTimeout(bar, 0) new Promise((resolve, reject) => resolve('应该在 baz 之后、bar 之前') ).then(resolve => console.log(resolve)) baz() } foo() /* 输出结果: foo baz 应该在 baz 之后、bar 之前 bar */这是Promise(以及基于 promise 构建的 async / await)与通过 setTime() 或其他平台 API 的普通的旧异步函数之间的巨大区别。
15. 了解 process.nextTick()
每当事件循环进行一次完整的行程时,我们都将其称为一个滴答。
当将一个函数传给 process.nextTick() 时,则指示引擎在当前操作结束(在下一个事件循环滴答开始之前)时调用此函数:
process.nextTick(() => {
// 做一些事情
})
事件循环正在忙于处理当前的函数代码。
当该操作结束时,JS 引擎会运行在该操作期间传给 nextTick 调用的所有函数。
调用 setTimeout(() => {}, 0) 会在下一个滴答结束时执行该函数,比使用 nextTick()(其会优先执行该调用并在下一个滴答开始之前执行该函数)晚得多。
当要确保在下一个事件循环迭代中代码已被执行,则使用 nextTick()
16. 了解 setImmediate()
当要异步地(但要尽可能快)执行某些代码时,其中一个选择是使用 Node.js 提供的 setImmediate() 函数:
setImmediate(() => {
//运行一些东西
})
作为 setImmediate() 参数传入的任何函数都是在事件循环的下一个迭代中执行的回调。
setImmediate() 与 setTimeout(() => {}, 0)(传入 0 毫秒的超时)、process.nextTick() 有何不同?
传给 process.nextTick() 的函数会在事件循环的当前迭代中(当前操作结束之后)被执行。 这意味着它会始终在 setTimeout 和 setImmediate 之前执行。
延迟 0 毫秒的 setTimeout() 回调与 setImmediate() 非常相似。 执行顺序取决于各种因素,但是它们都会在事件循环的下一个迭代中运行。
17. 探索 JavaScript 定时器
-
setTimeout()延迟函数。指定一个回调函数以稍后执行,并指定希望它稍后运行的时间(以毫秒为单位)的值:
setTimeout(() => { // 2秒后运行 }, 2000) setTimeout(() => { // 50毫秒后运行 }, 50)清除延迟函数。
let id = setTimeout(() => { // 应该要在2秒后运行 }, 2000) // 不想让它运行了 clearTimeout(id) -
零延迟
如果将延迟时间设置为 0,则回调函数会被尽快执行(但是是在当前函数执行之后):
setTimeout(() => { console.log('1') }, 0) console.log('0') // 结果是 0 1通过在调度程序中欧给排队函数,可以避免在执行繁重的任务时阻塞 CPU,并在执行繁重的计算时执行其他函数。
某些浏览器(IE 和 Edge)实现的
setTmmediate()方法具有相同的确切功能,但是不是标准的,并且在其他浏览器上不可用。但是在 Node.js 中它是标准的函数 -
setInterval()setInterval()是一个类似于setTimeout的函数,不同之处在于:它会在指定的特定时间间隔(以毫秒为单位)一直地运行回调函数,而不是只运行一次。setInterval(() => { // 每 2 秒运行一次 }, 2000)上面的函数会在每 2 秒运行一次,除非使用
clearInterval告诉它停止(传入setInterval返回的间隔定时器 id):let id = setInterval(() => { // 每 2 秒运行一次 }, 2000) clearInterval(id)通常在
setInterval回调函数中调用clearInterval,以使其自行判断是否应该再次运行或停止。let interval = setInterval(() => { if(// 判断条件){ clearInterval(interval) return } // 否则做些事情 }, 100) -
递归的 setTimeout
setInterval每 n 毫秒启动一个函数,而无需考虑函数何时完成执行。如果一个函数总是花费相同的时间,那就没问题了:

但是函数可能需要不同的执行时间,这具体取决于网络条件,例如:

也许一个较长时间的执行会与下一次执行重叠:

为了避免这些情况,可以在回调函数完成时安排要被调用的递归的 setTimeout:
let myFunction = () => { // 做一些事情 setTimeout(myFunction, 1000) } setTimeout(myFunction, 1000)下面是此代码的执行过程:

Node.js 还提供
setImmediate()(相当于使用setTimeout(() = > {}, 0),通常用于与 Node.js 事件循环配合使用。
18. JavaScript 异步编程与回调
-
编程语言中的异步性
异步意味着事情可以独立于主程序流而发生。
在当前的用户计算机中,每个程序都运行于特定的时间段,然后停止执行,以让另一个程序继续执行。这件事运行得如此之快,以至于无法察觉。我们以为计算机可以同时运行许多程序,但是这是一种错觉(在多处理器计算机上除外)。
程序在内部会使用中断,一种被发送到处理器以获取系统关注的信号。
程序是异步的,且会暂停执行直到需要关注,这使得计算机可以同时执行其他操作。当程序正在等待来自网络的响应时,则它无法在请求完成之前停止处理器。
通常,编程语言是同步的,有些会在语言或库中提供管理异步性的方法。 默认情况下,C、Java、C#、PHP、Go、Ruby、Swift 和 Python 都是同步的。 其中一些语言通过使用线程(衍生新的进程)来处理异步操作。
-
JavaScript
JavaScript 默认下是同步的,并且是单线程的。这意味着代码无法创建新的线程并且不能并行运行。
代码行是依次执行的。
但是 JavaScript 诞生于浏览器内部,一开始的主要工作是响应用户的操作,例如
onClick、onMouseOver、onChange、onSubmit等。 使用同步的编程模型该如何做到这一点?答案就在于它的环境。 浏览器通过提供一组可以处理这种功能的 API 来提供了一种实现方式。
更近点,Node.js 引入了非阻塞的 I/O 环境,以将该概念扩展到文件访问、网络调用等。
-
回调
因为不知道用户什么时候会单击按钮。因此,我们会为点击事件定义一个事件处理程序。这个事件处理程序会接收一个函数,这个函数会在该事件被触发的时候被调用:
doucument.getElementById('button').addEventListener('click', () => { // 被点击 })这就是所谓的回调。
回调时是一个简单的函数,会作为值被传给另外一个函数,并且仅在事件发生时才被执行。之所以这样做,是因为 JavaScript 具有顶级的函数,这些函数可以被分配给变量并传给其他函数(称为高阶函数)。
通常我们都会将所有的客户端代码封装在
window对象的load事件监听器中,其仅在页面准备就绪时才会运行回调函数:window.addEventListener('load', () => { // window 已被加载 // 做需要做的。 })定时器也是用到了回调。
-
处理回调中的错误
如何处理回调的错误?一种非常常见的策略是使用 Node.js 所采用的方式:任何回调函数中的第一个参数为错误对象(即错误优先的回调)
如果没有错误,则该对象为
null。如果有错误,则它会包含对该错误的描述以及其他信息。fs.readFile('/文件.json', (err, data) => { if(err){ console.log(err.message) return } console.log(data) }) -
回调的问题
回调适用于简单的场景!
但是回调可以进行嵌套,并且当有很多回调的时候,代码就会很快变得非常复杂:
window.addEventListener('load', () => { document.getElementById('button').addEventListenner('click', () => { setTimeout(() => { items.forEach(item => { // 这是要运行的代码 }) },) }) }) -
回调的替代方法
从 ES6 开始,JavaScript 引入了一些特性,可以帮助处理异步代码而不涉及使用回调:Promise(ES6)和 Async/Await(ES2017)。
19. 了解 Promise
-
Promise 简介
Promise 通常被定义为最终会变为可用值的代理。
Promise 是一种处理异步代码(而不会陷入回调地狱)的方式。
多年来,promise 已成为语言的一部分,并且最近变得更加集成,在 ES2017 中具有了 async 和 await。
异步函数 在底层使用了 promise,因此了解 promise 的工作方式是了解
async和await的基础。 -
Promise 如何运作
当 promise 被调用后,它会以处理中状态开始。这意味着调用的函数会继续执行,而 promise 仍处于处理中直到解决为止,从而为调用的函数提供所请求的任何数据。被创建的 promise 最终会以 被解决状态 或 被拒绝状态 结束,并在完成时调用相应的回调函数(传给
then和catch)。 -
哪些 JS API 使用了 promise?
除了自己的代码和库代码,标准的现代 Web API 也使用了 promise,例如:
- Battery API
- Fetch API
- Service Worker
在现代JavaScript中,不太可能没有使用 promise。
-
创建 promise
Promise API 公开了一个 Promise 构造函数,可以使用
new Promise()对其进行初始化:let done = true const isItDoneYet = new Promise((resolve, reject) => { if(done){ const workDone = '这是创建的东西' resolve(workDone) }else{ const why = '仍然在处理其他事情' reject(why) } })promise 检查了
done全局常量,如果为真,则 promise 进入被解决状态(因为调用了resolve回调);否则,则执行reject回调(将 promise 置于被拒绝状态)。如果在执行路径中从未调用过这些函数之一,则 promise 会保持处理中状态。使用
resolve和reject,可以向调用者传达最终的 promise 状态以及该如何处理。在上述示例中,只返回了一个字符串,但是它可以时一个对象,也可以为null。由于已经在上述的代码片段中创建了 promise,因此它已经开始执行。这对了解下面的消费 promise 章节很重要。一个更常见的示例是一种被称为 Promisifying 的技术。这项技术能够使用经典的 JavaScript 函数来接收回调并使其返回 promise:
const fs = require('fs') const getFile = (fileName) => { return new Promise((resolve, reject) => { fs.readFile(fileNmae, (err, data) => { if(err){ reject(err) // 调用 `reject` 会导致 promise 失败,无论是否传入错误作为参数 return // 且不再进行下去 } resolve(data) }) }) } getFile('/etc/passwd') .then(data => console.log(data)) .catch(err => console.log(err))在最新版本的 Node.js 中,无需为大多数 API 进行手动地转换。如果需要 promisifying 的函数具有正确的签名,则 util 模块中有一个 promisifying 函数可以完成此操作。
-
消费 promise
上面介绍了如何创建 promise。
下面讲讲如何消费或使用 promise。
const isItDoneYet = new Promise(/* ... 如上所述 ... */) cosnt checkIfItsDone = () => { isItDoneYet .then(ok => { console.log(ok) }) .catch(err => { console.error(err) }) }运行
checkIfItsDone()会指定当isItDoneYetpromise 被解决(在then调用中)或被拒绝(在catch调用中)时执行的函数。 -
链式 promise
Promise 可以返回到另一个 promise,从而创建一个 promise 链。
链式 promise 的一个很好的示例时 Fetch API,可以用于获取资源,且当资源被获取时将 promise 链式排队进行执行。
Fetch API 是基于 promise 的机制,调用
fetch()相当于使用new Promise()来定义promise。 -
链式 promise 的示例
const status = response => { if (response.status >= 200 && response.status < 300) { return Promise.resolve(response) } return Promise.reject(new Error(response.statusText)) } const json = response => response.json() fetch('/todos.json') .then(status) // 注意,`status` 函数实际上在这里被调用,并且同样返回 promise, .then(json) // 这里唯一的区别是的 `json` 函数会返回解决时传入 `data` 的 promise, .then(data => { // 这是 `data` 会在此处作为匿名函数的第一个参数的原因。 console.log('请求成功获得 JSON 响应', data) }) .catch(error => { console.log('请求失败', error) })在此示例中,调用
fetch()从域根目录中的todos.json文件中获取 TODO 项目的列表,并创建一个 promise 链。运行
fetch()会返回一个响应,该响应具有许多属性,在属性中引用了:status,表示 HTTP 状态码的数值。statusText,状态消息,如果请求成功,则为OK。
response还有一个json()方法,该方法会返回一个 promise,该 promise 解决时会传入已处理并转换为 JSON 的响应体的内容。因此,考虑到这些前提,发生的过程是:链中的第一个 promise 是我们定义的函数,即
status(),它会检查响应的状态,如果不是成功响应(介于 200 和 299 之间),则它会拒绝 promise。此操作会导致 promise 链跳过列出的所有被链的 promise,且会直接跳到底部的
catch()语句(记录请求失败的文本和错误消息)。如果成功,则会调用定义的
json()函数。 由于上一个 promise 成功后返回了response对象,因此将其作为第二个 promise 的输入。在此示例中,返回处理后的 JSON 数据,因此第三个 promise 直接接收 JSON:
.ther((data) => { console.log('请求成功获得 JSON 响应', data) })只需将其记录到控制台即可。
-
处理错误
在上面的示例中,有个
catch被附加到了 promise 链上。当 promise 链中的任何内容失败并引发错误或拒绝promise时,则控制权会转到链中最近的
catch()语句。new Promise((resolve, reject) => { throw new Error('错误') }).catch(err => { console.error(err) }) // 或 new Promise((resolve, reject) => { reject('错误') }).catch(err => { console.error(err) }) -
级联错误
如果在
catch()内部引发错误,则可以附加第二个cathch()来处理,依此类推。new Promise((resolve, reject) => { throw new Error('错误') }) .catch(err => { throw new Error('错误') }) .catch(err => { console.error(err) }) -
编排 promise
Promise.all()如果需要同步不同的 promise,则
Promise.all()可以帮助定义 promise 列表,并在所有 promise 都被解决后执行一些操作。示例:
const f1 = fetch('/something.json') const f2 = fetch('/something2.json') Promise.all([f1, f2]) .then(res => { console.log('结果的数组', res) }) .catch(err => { console.error(err) })ES2015 解构赋值语法也可以执行:
Promise.all([f1, f2]).then(([res1, res2]) => { console.log('结果', res1, res2) })Promise.race()当传给首个 promise 被解决时,则
Promise.race()开始运行,并且只运行一次附加的回调(传入第一个被解决的 promise 的结果)示例:
const first = new Promise((resolve, reject) => { setTimeout(resolve, 500, '第一个') }) const second = new Promise((resolve, reject) => { setTimeout(resolve, 100, '第二个') }) Promise.race([first, second]).then(result => { console.log(result) // 第二个 }) -
常见错误
Uncaught TypeError: undefined is not a promise
如果在控制台中收到
Uncaught TypeError: undefined is not a promise错误,则请确保使用new Promise()而不是Promise()。UnhandledPromiseRejectionWarning
这意味着调用的 promise 被拒绝,但是没有用于处理错误的
catch。 在then之后添加catch则可以正确地处理。
20. 具有 Async 和 Await 的现代异步 JavaScript
-
介绍
JavaScript 在很短的事件内从回调发展到了 promise,从ES2017 以来,异步的 JavaScript 使用 async/await 语法甚至更加简单。
异步函数是 promise 和生成器的组合,基本上,它们是 promise 的更高级别的抽象。而 async/await 建立在 promise 之上。
-
为什么引入 async/await
它们减少了 promise 的样板,且减少了 promise 链的“不破坏链条”的限制。
promise 的引入确实解决了回调地狱的问题,但是也引入了复杂性以及语法复杂性。我们确实得到了异步函数,代码虽然看起来像是同步的。但是它是异步的并且在后台无阻塞。
-
工作原理
异步函数会返回 promise,例如以下示例:
const doSomethingAsync = () => { return new Promise(resolve => { setTimeout(() => resolve('做些事情'), 3000) }) }当要调用此函数时,则在前面加上
await,然后调用的代码就会停止直到 promise 被解决或被拒绝。注意:客户端函数必须被定义为async。这是一个示例:const doSomething = async () => { console.log(await doSomethingAsync()) } -
一个简单的例子:
这是一个 async/await 的简单示例,用于异步地运行函数:
const doSomethingAsync = () => { return new Promise(resolve => { setTimeout(() => resolve('做些事情'), 3000) }) } const doSomething = async () => { console.log(await doSomethingAsync()) } console.log('之前') doSomething() console.log('之后') /* 打印结果 之前 之后 (3秒之后) 做些事情 */ -
Promise 所有事情
在任何函数之前加上
async关键字意味着该函数会返回 promise。下面两种代码是同一个结果:
const aFunction = async () => { return '测试' } aFunction().then(alert) // 这会 alert '测试'const aFunction = () => { return Promise.resolve('测试') } aFunction().then(alert)// 这会 alert '测试' -
代码更容易阅读
从上面的两个例子可以看出,用 async 的代码就看起来很简单。
下面子再看个例子,这是使用 promise 获取并解析 JSON 资源的方法:
const getFirstUserData = () => { return fetch('/users.json') // 获取用户列表 .then(response => response.json()) // 解析 JSON .then(users => users[0]) // 选择第一个用户 .then(user => fetch(`/users/${user.name}`)) // 获取用户数据 .then(userResponse => userResponse.json()) // 解析 JSON } getFirstUserData()这是使用 await/async 提供的相同功能:
JSconst getFirstUserData = async () => { const response = await fetch('/users.json') // 获取用户列表 const users = await response.json() // 解析 JSON const user = users[0] // 选择第一个用户 const userResponse = await fetch(`/users/${user.name}`) // 获取用户数据 const userData = await userResponse.json() // 解析 JSON return userData } getFirstUserData() -
多个异步函数串联
异步函数可以很容易地链接起来,并且语法比普通的 promise 更具可读性:
JSconst promiseToDoSomething = () => { return new Promise(resolve => { setTimeout(() => resolve('做些事情'), 10000) }) } const watchOverSomeoneDoingSomething = async () => { const something = await promiseToDoSomething() return something + ' 查看' } const watchOverSomeoneWatchingSomeoneDoingSomething = async () => { const something = await watchOverSomeoneDoingSomething() return something + ' 再次查看' } watchOverSomeoneWatchingSomeoneDoingSomething().then(res => { console.log(res) }) /* 打印结果 SH 做些事情 查看 再次查看 */ -
更容易调试
调试 promise 很难,因为调试器不会跳过异步的代码。
Async/await 使这非常容易,因为对于编译器而言,它就像同步代码一样。
21. Node.js 事件触发器
如果你在浏览器中使用 JavaScript,则你会知道通过事件处理了许多用户的交互:鼠标的单击、键盘按钮的按下、对鼠标移动的反应等等。
在后端,Node.js 也提供了使用 events 模块构建类似系统的选项。
使用以下代码进行 EventEmitter 类的初始化:
const EventEmitter = require('events')
const eventEmitter = new EventEmitter()
该对象公开了 on 和 emit 方法。
1. `emit` 用于触发事件。
2. `on` 用于添加回调函数(会在事件被触发时执行)。
例如,创建 start 事件,并提供一个示例,通过记录到控制台进行交互:
eventEmitter.on('start', () => {
console.log('开始')
})
运行下面的代码就是在触发 start 时间就,且获得控制台日志:
eventEmitter.emit('start')
可以通过将参数作为额外参数传给 emit() 来将参数传给事件处理程序:
eventEmitter.on('start', number => {
console.log(`开始 ${number}`)
})
eventEmitter.emit('start', 20)
eventEmitter.on('start', (start, end) => {
console.log(`从 ${start} 到 ${end}`)
})
eventEmitter.emit('start', 0, 100)
EventEmitter 对象还公开了其他几个与事件进行交互的方法,例如:
1. `once()`:添加单次监听器
2. `removeListener()` / `off()` :从事件中移除事件监听器
3. `removeAllListeners()` :移除事件的所有监听器
四、模块
1. HTTP 模块
Ⅰ、 HTTP 模块是什么?有什么用?
HTTP 模块是 Node.js 官方提供的、用来创建web 服务器的模块。通过 HTTP 模块提供的 createServer() 方法,就能方便的把一台普通的电脑,变成一台 Web 服务器,从而对外提供 Web 资源服务。
在 Node.js 中,我们不需要使用 IIS、Apache 等这些第三方 web 服务器软件。可以基于 Node.js 提供的 HTTP模块,通过几行简单的代码就能手写一个服务器软件,从而对外提供 web 服务。
Ⅱ、搭建一个简单的 HTTP 服务器
创建 web 服务器的基本步骤
1. 导入 HTTP 模块
2. 创建 web 服务器实例
3. 为服务器实例绑定 request 事件,监听客户端的请求
4. 启动服务器
const http = require('http')
const port = 80
const server = http.createServer()
server.on('request', (req, res) => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('你好世界')
})
server.listen(port, () => {
console.log(`服务器运行在 http://${hostname}:${port}/`)
})
服务器被设置在指定的 80 端口上进行监听。当服务器启动后,则 listen 回调函数会被调用。
传入的回调函数会在每次接收到请求时被执行。每当接收到新的请求时, request 事件就会被调用,并提供两个对象,一个请求对象(req)和一个响应对象(res)。
req 对象可以访问与客户端相关的数据和属性。
例如:
- req.url:是客户端请求的 url 地址
- req.method:是客户端请求的类型
res 对象可以访问服务器相关的数据或属性。
例如:
- res.end():向客户端发送指定的内容,并结束这次请求的处理过程。
解决中文乱码问题
在 setHeader 设置 charset=utf-8 Content-Type
Ⅲ、根据不同的 url 响应不同的 html 内容
核心实现步骤:
1. 获取请求的 url 地址
2. 设置默认的响应内容为 404 Not found
3. 判断用户请求的是否为 / 或 /index.html 首页
4. 判断用户请求的是否为 /a A页面
5. 设置 Content-Type 响应头,防止中文乱码
6. 使用 res.end() 把内容响应给客户端
server.on('request', function(req, res){
const url = req.url
let content = '<h1>404 Not fount!</h1>'
if(url === '/' || url === '/index.html'){
content = '<h1>首页</h1>'
} else if(url === '/a.html'){
content = '<h1>A页面</h1>'
}
res.setHeader('Content-Type','text/html;charset=utf-8')
res.end(content)
})
Ⅳ、使用 Node.js 发送 HTTP 请求
-
执行 GET 请求
const https = require('https') const options = { hostname: 'nodejs.cn', port: 443, path: '/todos', method: 'GET' } const req = https.request(options, res => { console.log(`状态码: ${res.statusCode}`) res.on('data', d => { process.stdout.write(d) }) }) req.on('error', error => { console.error(error) }) req.end() -
执行 POST 请求
const https = require('https') const data = JSON.stringify({ todo: '做点事情' }) const options = { hostname: 'nodejs.cn', port: 443, path: '/todos', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': data.length } } const req = https.request(options, res => { console.log(`状态码: ${res.statusCode}`) res.on('data', d => { process.stdout.write(d) }) }) req.on('error', error => { console.error(error) }) req.write(data) req.end() -
PUT 和 DELETE
PUT 和 DELETE 请求使用相同的 POST 请求格式,只需要更改
options.method的值即可。
Ⅴ、使用 Node.js 发送 HTTP POST 请求
在 Node.js 中,有多种方式可以执行 HTTP POST 请求,具体取决于要使用的抽象级别。
使用 Node.js 执行 HTTP 请求的最简单的方式是使用 Axios 库:
const axios = require('axios')
axios
.post('http://nodejs.cn/todos', {
todo: '做点事情'
})
.then(res => {
console.log(`状态码:${res.statusCode}`)
console.log(res)
})
.catch(error => {
console.error(error)
})
Axios 需要使用第三方的库。
也可以只使用 Node.js 的标准模块来发送 POST 请求,尽管它比前面的选择冗长些。例如上面的方式。
2. fs 文件系统模块
Ⅰ、什么是 fs 文件系统模块
fs 模块是 Node.js 官方提供的、用来操作文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求。
例如:
fs.readFile() 方法,用来读取指定文件中的内容
fs.writeFile() 方法,用来向指定文件中写入内容
Ⅱ、读取指定文件中的内容
使用 fs.readFile() 方法,可以读取指定文件中的内容:
fs.readFile(path[,options], callback)
参数1:必选参数,字符串,表示文件的路径
参数2:可选参数,表示以什么编码格式来读取文件
参数3:必选参数,文件读取完成后,通过回调函数拿到读取的结果
const fs = require('fs')
fs.readFile('./files/1.txt', 'utf8', function(err, dataStr) {
if(err){
return console.log('文件读取失败' + err.message)
}
console.log('文件读取成功' + dataStr)
})
Ⅲ、向指定的文件中写入内容
使用 fs.writeFile() 方法,可以向指定的文件中写入内容:
fs.writeFile(file, data[, options], callback)
参数1:必选参数,需要指定一个文件路径的字符串,表示文件的存放路径
参数2:必选参数,表示要写入的内容
参数3:可选参数,表示以什么格式写入文件内容,默认值是 utf8
参数4:必选参数,文件写入完成后的回调函数
const fs = require('fs')
fs.writeFile('./file/2.txt', 'Hello Node.js', 'utf8',function(err) {
if(err){
return console.log('文件写入失败' + err.message)
}
console.log('文件写入成功')
})
Ⅳ、练习
// 1. 导入 fs 模块
const fs = require('fs')
// 2. 调用 fs.readFile() 读取文件的内容
fs.readFile('../素材/成绩.txt', 'utf8', function(err, dataStr) {
// 3. 判断是否读取成功
if (err) {
return console.log('读取文件失败!' + err.message)
}
// console.log('读取文件成功!' + dataStr)
// 4.1 先把成绩的数据,按照空格进行分割
const arrOld = dataStr.split(' ')
// 4.2 循环分割后的数组,对每一项数据,进行字符串的替换操作
const arrNew = []
arrOld.forEach(item => {
arrNew.push(item.replace('=', ':'))
})
// 4.3 把新数组中的每一项,进行合并,得到一个新的字符串
const newStr = arrNew.join('\r\n')
// 5. 调用 fs.writeFile() 方法,把处理完毕的成绩,写入到新文件中
fs.writeFile('./files/成绩-ok.txt', newStr, function(err) {
if (err) {
return console.log('写入文件失败!' + err.message)
}
console.log('成绩写入成功!')
})
})
Ⅴ、fs 模块 - 路径动态拼接的问题
在使用 fs 模块操作文件时,如果提供的操作路径是以 ./ 或 …/ 开头的相对路径时,很容易出现路径动态拼接错误的问题。
原因:代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径。
解决方案:在使用 fs 模块操作文件时,直接提供完整的路径,不要提供 ./ 或 …/ 开头的相对路径,从而防止路径动态拼接的问题。
// __dirname 表示当前文件所处的目录
fs.readFile(__dirname + '/files/1.txt', 'utf8', function(errr, dataStr){
if(err) return console.log('读入文件错误' + err.message)
console.log(dataStr)
})
3. path 路径模块
Ⅰ、什么是 path 路径模块
path 模块是 Node.js 官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求。
例如:
- path.join() 方法:用来将多个路径片段拼接成一个完整的路径字符串。
- path.basename() 方法:用来从路径字符串中,将文件名解析出来。
Ⅱ、路径拼接
使用 path.join() 方法,可以把多个路径片段拼接为完整的路径字符串:
…paths 路径片段的序列
返回值:
const pathStr = path.join('/a', '/b/c', '../', './d', 'e')
console.log(pathStr) // 输出\a\b\d\e
const pathStr2 = path.join(__dirname, './files/1.txt')
console.log(pathStr2) // 输出当前文件所在目录\files\1.txt
Ⅲ、获取路径中的文件名
使用 path.basename() 方法,可以获取路径中的最后一部分,经常通过这个方式获取路径中的文件名:
path 必选参数,表示一个路径的字符串
ext 可选参数,表示文件拓展名
返回: 表示路径中的最后一部分
const fpath = '/a/b/c/index.html'
var fullName = path.basename(fpath)
consonle.log(fullName) // 输出 index.html
var nameWithoutExt = path.basename(fpath, '.html')
console.log(nameWithoutExt) // 输出 index
Ⅳ、获取路径中的文件拓展名
使用 path.extname() 方法,可以获取路径中的拓展名部分:
path 必选参数,表示一个路径的字符串
返回: 返回得到的扩展名字符串
const fpath = '/a/b/c/index.html'
const fext = path.extname(fpath)
console.log(fext) // 输出 .html
Ⅴ、练习
// 1.1 导入 fs 模块
const fs = require('fs')
// 1.2 导入 path 模块
const path = require('path')
// 1.3 定义正则表达式,分别匹配 <style></style> 和 <script></script> 标签
const regStyle = /<style>[\s\S]*<\/style>/
const regScript = /<script>[\s\S]*<\/script>/
// 2.1 调用 fs.readFile() 方法读取文件
fs.readFile(path.join(__dirname, '../素材/index.html'), 'utf8', function(err, dataStr) {
// 2.2 读取 HTML 文件失败
if (err) return console.log('读取HTML文件失败!' + err.message)
// 2.3 读取文件成功后,调用对应的三个方法,分别拆解出 css, js, html 文件
resolveCSS(dataStr)
resolveJS(dataStr)
resolveHTML(dataStr)
})
// 3.1 定义处理 css 样式的方法
function resolveCSS(htmlStr) {
// 3.2 使用正则提取需要的内容
const r1 = regStyle.exec(htmlStr)
// 3.3 将提取出来的样式字符串,进行字符串的 replace 替换操作
const newCSS = r1[0].replace('<style>', '').replace('</style>', '')
// 3.4 调用 fs.writeFile() 方法,将提取的样式,写入到 clock 目录中 index.css 的文件里面
fs.writeFile(path.join(__dirname, './clock/index.css'), newCSS, function(err) {
if (err) return console.log('写入 CSS 样式失败!' + err.message)
console.log('写入样式文件成功!')
})
}
// 4.1 定义处理 js 脚本的方法
function resolveJS(htmlStr) {
// 4.2 通过正则,提取对应的 <script></script> 标签内容
const r2 = regScript.exec(htmlStr)
// 4.3 将提取出来的内容,做进一步的处理
const newJS = r2[0].replace('<script>', '').replace('</script>', '')
// 4.4 将处理的结果,写入到 clock 目录中的 index.js 文件里面
fs.writeFile(path.join(__dirname, './clock/index.js'), newJS, function(err) {
if (err) return console.log('写入 JavaScript 脚本失败!' + err.message)
console.log('写入 JS 脚本成功!')
})
}
// 5.1 定义处理 HTML 结构的方法
function resolveHTML(htmlStr) {
// 5.2 将字符串调用 replace 方法,把内嵌的 style 和 script 标签,替换为外联的 link 和 script 标签
const newHTML = htmlStr.replace(regStyle, '<link rel="stylesheet" href="./index.css" />').replace(regScript, '<script src="./index.js"></script>')
// 5.3 写入 index.html 这个文件
fs.writeFile(path.join(__dirname, './clock/index.html'), newHTML, function(err) {
if (err) return console.log('写入 HTML 文件失败!' + err.message)
console.log('写入 HTML 页面成功!')
})
}
五、模块化
1. 模块化的基本概念
什么是模块化
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。
我们用的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。
将代码模块化拆分的好处:
- 提高了代码的复用性
- 提高了代码的可维护性
- 可以实现按需加载
2. Node.js 中的模块化
Ⅰ、Node.js 中的模块的分类
Node.js 中的模块主要分三类:
- 第一类:内置模块(也就是上面的 fs、path、http 等)
- 第二类:自定义模块(用户自己创建的 .js 文件,都是自定义模块)
- 第三类:第三方模块(由第三方开发出来的模块,使用需要下载)
Ⅱ、加载模块
用 require() 方法,可以加载需要的模块。
const fs = require('fs')
const custom = require('./custom.js')
const moment = require('moments')
Ⅲ、Node.js中的模块作用域
(一)什么是模块作用域
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。
简单来说。就是你在引入一个模块之后。如果这个模块没有把它的变量或方法等抛出。你是无法使用的。
(二)模块作用域的好处
防止全局变量污染
Ⅳ、向外共享模块作用域中的成员
module 对象
module.exports 对象
在自定义模块中,使用module.exports 对象,可以将模块中的成员共享出去。当在另一个地方使用 requier() 方法导入之后,就可以得到 module.exports 对象。
exports对象
exports 对象与 module.exports 对象的功能一样。默认情况下,exports 和 module.exports 指向同一个对象。最终共享的结果,还是以 module.exports 指向的对象为准。
使用 require() 方法导入模块时,导入的结果,永远以 module.exports 指向的对象为准。
exports 和 module.exports 的使用误区
要记住,只用用 require() 引入模块。得到的永远都是 module.exports 指向的对象。

注意:为了防止混乱,建议不要在同一个模块中同时使用 exports 和 module.exports
Ⅴ、Node.js 中的模块化规范
Node.js 遵循了 CommonJS 模块化规范,CommonJS 规定了模块的特性和各模块之间如何相互依赖。
CommonJS 规定:
- 每个模块内部,module 变量代表当前模块
- module 变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口。
- 加载某个模块,其实是加载该模块的 module.exports 属相。require() 方法用于加载模块。
Ⅵ、模块的加载机制
(一)优先从缓存中加载
模块在第一次加载后会被缓存。 这也意味着多次调用 require() 不会导致模块的代码被执行多次。
注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。
(二)内置模块的加载机制
内置模块是由 Node.js 官方提供的模块,内置模块的加载优先级最高。
例如,require(‘fs’) 始终返回内置的 fs 模块,即使在 node_modules 目录下有名字相同的包也叫做 fs。
(三)自定义模块的加载机制
使用 require() 加载自定义模块时,必须指定以 ./ 或 …/ 开头的路径标识符。在加载自定义模块时,如果没有指定 ./ 或 …/
这样的路径标识符,则 node 会把它当作内置模块或第三方模块进行加载。
同时,在使用 require() 导入自定义模块时,如果省略了文件的扩展名,则 Node.js 会按顺序分别尝试加载以下的文件:
- 按照确切的文件名进行加载
- 补全 .js 扩展名进行加载
- 补全 .json 扩展名进行加载
- 补全 .node 扩展名进行加载
- 加载失败,终端报错
(四)第三方模块的加载机制
如果传递给 require() 的模块标识符不是一个内置模块,也没有以 ‘./’ 或 ‘…/’ 开头,则 Node.js 会从当前模块的父
目录开始,尝试从 /node_modules 文件夹中加载第三方模块。
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
例如,假设在 ‘C:\Users\itheima\project\foo.js’ 文件里调用了 require(‘tools’),则 Node.js 会按以下顺序查找:
- C:\Users\itheima\project\node_modules\tools
- C:\Users\itheima\node_modules\tools
- C:\Users\node_modules\tools
- C:\node_modules\tools
(五)目录作为模块
当把目录作为模块标识符,传递给 require() 进行加载的时候,有三种加载方式:
- 在被加载的目录下查找一个叫做 package.json 的文件,并寻找 main 属性,作为 require() 加载的入口
- 如果目录里没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件。
- 如果以上两步都失败了,则 Node.js 会在终端打印错误消息,报告模块的缺失:Error: Cannot find module ‘xxx’
六、Express
1. 初始Express
Ⅰ、什么是 Express
Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架。
其实就是与 Node.js 的内置的 http 模块很类似,是专门用来创建 Web服务器是。
Express 其实可以理解为 npm 上的一个第三方包。提供了快速创建 Web 服务器的便捷方法。
Express 就是用 http 内置模块封装出来的。
Ⅱ、Express 的基本使用
(一)安装
npm i express
(二)创建基本的 Web 服务器
const express = require('express')
const app = express()
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
(三)监听 GET 请求
通过 app.get() 方法,可以监听客户端的 GET 请求:
// 参数1:客户端请求的 url 地址
// 参数2:请求对应的处理函数
// req:请求对象(包含了请求相关的属性和方法)
// res:响应对象(包含了响应相关的属性于方法)
app.get('请求URL', functionI(req, res) => { /*处理函数*/})
(四)监听 POST 请求
通过 app.post() 方法,可以监听客户端的 POST 请求:
// 参数1:客户端请求的 URL 地址
// 参数2:请求对应的处理函数
// req:请求对象(包含了与请求相关的属性和方法)
// res:响应对象(包含了与响应相关的属性和方法)
app.post('请求URL', function(req, res) => { /*处理函数*/})
(五)把内容响应给客户端
通过 res.send() 方法,可以把处理好的内容,发送给客户端:
app.get('/user', (req, res) => {
// 调用 express 提供的 res.send() 方法,向客户端响应一个 JSON 对象
res.send({ name: 'zs', age: 20, gender: '男' })
})
app.post('/user', (req, res) => {
// 调用 express 提供的 res.send() 方法,向客户端响应一个 文本字符串
res.send('请求成功')
})
(六)获取 URL 中携带的查询参数
通过 req.query 对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数:
app.get('/', (req, res) => {
// 通过 req.query 可以获取到客户端发送过来的 查询参数
// 注意:默认情况下,req.query 是一个空对象
console.log(req.query)
res.send(req.query)
})
// 如果客户端使用 ?name=zs&age=19
// 则可以访问到 req.query.name 或 req.query.age
(七)获取 URL 中的动态参数
通过 req.params 对象,可以访问到 URL 中,通过:匹配到的动态参数:
// 注意:这里的 :id 是一个动态的参数
app.get('/user/:ids/:username', (req, res) => {
// req.params 是动态匹配到的 URL 参数,默认也是一个空对象
console.log(req.params)
res.send(req.params)
})
(八)托管静态资源
express 提供了一个非常好用的函数,叫做 express.static(),通过它,我们可以非常方便地创建一个静态资源服务器,例如下面我们将 public 目录下的图片、CSS 文件、JavaScript 文件对外开放访问:
app.use(express.static('public'))
如果想要托管多个静态资源。就多次调用 express.static 就可以了。
如果希望在托管的静态资源访问路径之前,挂载路径前缀:
app.use('/public', express.static('public'))
(九)使用 nodemon
用 node 运行文件需要:
node 文件名
而且修改一次代码就需要重新运行一下。这样比较麻烦。所以我们引用了 nodemon
先进行安装:
npm install -g nodemon
之后就可以用了:
nodemon 文件名
2. Express 路由
Ⅰ、路由的概念
简单来说。路由就是映射。
Express 中的路由,就是指客户端和服务器处理函数之间的映射关系。路由由三部分组成,请求的类型、请求的 URL 地址、处理函数。
app.METHOD(PATH, HANDLER)
路由的匹配过程:每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功了。才会调用对应的处理函数。在匹配的时候,需要匹配请求类型和请求的 URL 。当两者都匹配了。Express会把这次请求,转交给对应的处理函数进行解决。
Ⅱ、路由的使用
最简单的形式,就是把路由挂到 express 的实例对象 app 上。
cont express = require('express')
const app = express()
app.get('/', (req, res) => {
res.send('Hello World')
})
app.post('/', (req, res) => {
res.send('Hello Request')
})
app.listen(80, () => {
console.log('server running at http://127.0.0.1')
})
模块化路由:为了方便对路由进行模块化的管理,Express 不建议直接把路由挂在 app 上,而是推荐将路由抽离为单独的模块。
步骤:
- 创建路由模块对应的 .js 文件
- 调用 express.Router() 函数创建路由对象
- 向路由对象上挂载具体的路由
- 使用 module.exports 向外共享路由对象
- 使用 app.use() 函数注册路由模块
创建路由模块
var express = require('express')
var router = express.Router()
router.get('/user/list', function(req, res) {
res.send('Get user list')
})
router.post('/user/list', function(req, res) {
res.send('Add new user')
})
module.exports = router
注册路由模块
const userRouter = require('./router/user.js')
app.use('/api', userRouter)
3. Express 中间件
Ⅰ、中间件的概念
什么是中间件呢:中间件,特指业务流程的中间处理环节。
Express 中间件的调用流程:当一个请求到达 Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。
Express 中间件的格式:它本质就是一个 function 处理函数。其其中要注意的就是,中间件的形参列表中,必须包含 next 参数,而路由处理函数中只包含 req 和 res

其中 next 函数的作用:next 函数是实现多个中间件连续调用的关键,它把流转关系转交给下一个中间件或路由。

Ⅱ、Express 中间件的初体验
定义中间件函数:下面是一个最简单的中间件函数
const mw = function (req, res, next) {
console.log('这是一个简单的中间件函数')
next()
}
全局生效的中间件:客户端发起任何请求,到达服务器之后,都会触发的中间件,就叫全局生效的中间件。只需要通过app.use(中间件函数) 就可以定义一个全局生效的中间件。
// 比如我想将上述的中间件函数定义到全局生效
app.use(mw)
中间件的作用:多个中间件之间共享了同一份 req 和 res。基于这样的特性,我们可以在上游的中间件中,统一为 req 或 res 对象添加自定义的属性或方法,供下游的中间件或路由进行使用。

定义多个全局中间件:使用 app.use 连续定义多个全局中间件,客户端的请求到底服务器之后,就会按照定义中间件的先后顺序依次调用。
app.use(function(req, res, next){
console.log('调用了第一个中间件')
next()
})
app.use(function(req, res, next){
console.log('调用了第二个中间件')
next()
})
app.use(function(req, res, next){
console.log('调用了第三个中间件')
next()
})
app.get('/user', (req, res) => {
res.send('Home page')
})
局部生效的中间件:和上述的全局作用域相反。
const mw1 = function(req, res, next) {
console.log('这是中间件函数')
next()
}
// mw1 这个中间件只在"当前路由中生效",这种用法属于"局部生效的中间件"
app.get('/', mw1, function(req, res){
res.send('Home page')
})
// mw1 不会影响到下面的路由
app.get('/user', function(req, res) {
res.send('User page')
})
当需要定义多个中间件的时候,可以有两种做法
app.get('/', mw1, mw2, (req, res) => {
res.send('Home page')
})
app.get('/',[mw1, mw2], (req, res) => {
res.send('Home page')
})
中间件的5个使用注意事项
- 一定要在路由之前注册中间件
- 客户端发送过来的请求,可以连续调用多个中间件进行处理
- 执行完中间件的业务代码之后。不要忘记调用 next() 函数
- 为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码
- 连续调用多个中间件时,多个中间件之间,共享 req 和 res
中间件的分类
第一类:应用级别的中间件
通过 app.use() 或 app.get() 或 app.post(),绑定到 app 实例上的中间件,叫做应用级别的中间件:
app.use((req, res, next) => {
next()
})
app.get('/', mw1, (req, res) => {
res.send('Home page')
})
第二类:路由级别的中间件
绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到 app 实例上,路由级别中间件绑定到 router 实例上:
var app = express()
var router = express.Router()
router.use(function(req, res, next){
console.log('Time', Date.now())
next()
})
app.use('/', router)
第三类:错误级别的中间件
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
app.get('/', function(req, res){
throw new Error('服务器内部发生了错误!')
res.send('Home page')
})
app.use(function(err, req, res, next) {
console.log('发生了错误:' + err.message)
res.send('Error!' + err.message)
})
**注意:**错误级别中间件,必须注册在所有路由之后
第四类:Express 内置的中间件
Express 内置了 3 个常用的中间件:
express.static 快速托管静态资源的内置中间件(无兼容)
express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
express.urlencoded 解析 URL -encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
第五类:第三方的中间件
非 Express 官方内置的。而是第三方开发出来的。需要下载并配置之后使用。
4. 使用 Express 写接口
Ⅰ、创建基本的服务器
const express = require('express')
const app = express()
app.listen(80, function() {
console.log('Express server running at http://127.0.0.1')
})
Ⅱ、创建 API 路由模块
// apiRouter.js
const express = require('express')
const apiRouter = express.Router()
module.exports = apiRouter
// app.js
const apiRouter = require('./apiRouter.js')
app.use('/api', apiRouter)
Ⅲ、编写 GET 接口
apiRouter.get('/get', (req, res) => {
const query = req.query
res.send({
status:0,
msg:'GET 请求成功',
data:query
})
})
Ⅳ、编写 POST 接口
apiRouter.post('./post', (req, res) => {
const body = req.body
res.send({
status:0,
msg:'POST 请求成功',
data:body
})
})
Ⅴ、CORS 跨域资源共享
**接口的跨域问题:**上面编写的 GET 和 POST 接口,存在一个很严重的问题:不支持跨域请求。
解决接口跨域问题的方案主要有两种:
- CORS(主流的解决方案,推荐使用)
- JSONP(有缺陷的解决方案:只支持 GET 请求)
使用 cors 中间件解决跨域问题:cors 是 Express 的一个第三方中间件。通过安装和配置 cors 中间件,可用很方便的解决跨域问题。
使用步骤:
- 安装中间件 npm install cors
- 导入中间件 const cors = require(‘cors’)
- 在路由之前调用 app.use(cors()) (调用中间件)
**CORS 是什么:**CORS 由一系列 HTTP 响应头组成,这些响应头决定浏览器是否阻止前端 js 代码跨域获取资源。浏览器存在同源策略。不过当接口服务器配置了 CORS 相关的 HTTP 响应头,就可用解除浏览器端的跨域访问限制
CORS 的注意事项:
- CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口
- CORS 在浏览器中有兼容性。只有支持 XMLHttpReuest Level2 的浏览器,才能正常访问开启 CORS 的服务器端口(例如:IE10+、Chrome4+、FireFox3.5+)
CORS 响应头部 - Access-Control-Allow-Origin
设置为只允许来自 http://a.cn 的请求
res.setHeader('Access-Control-Allow-Origin', 'http://a.cn')
设置为所有都允许请求
res.setHeader('Access-Control-Allow-Origin', '*')
CORS 响应头部 - Access-Control-Allow-Headers
默认情况下,CORS 仅支持客户端向服务器发送如下的 9 个请求头:
Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、
Content-Type (值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 Access-Control-Allow-Headers 对额外
的请求头进行声明,否则这次请求会失败!
**CORS 响应头部 **- Access-Control-Allow-Methods
默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。
如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods
来指明实际请求所允许使用的 HTTP 方法。
// 只允许 POST, GET, DELETE, HEAD 请求方法
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, HEAD')
// 允许所有请求方法
res.setHeader('Access-Control-Allow-Methods', '*')
CORS 请求的分类
-
简单请求
同时满足以下两大条件的请求,就属于简单请求:
-
请求方式:GET、POST、HEAD 三者之一
-
HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type(只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)
-
-
预检请求
同时满足以下任何一个条件的请求,都需要进行预检请求:
- 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
- 请求头中包含自定义头部字段
- 向服务器发送了 application/json 格式的数据
在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一次的 OPTION 请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
简单请求和预检请求的区别
简单请求的特点:客户端与服务器之间只会发生一次请求
预检请求的特点:客户端与客户端之间会发生两次请求,OPTION 预检请求成功之后,才会发起真正的请求。
Ⅵ、JSONP 接口
创建 JSONP 接口的注意事项
如果项目中已经配置了 CORS 跨域资源共享,要在 CORS 中间件之气那声明 JSONP 的接口。否则 JSONP 接口会被处理成开启了 CORS 的接口。
app.get('/api/jsonp', (req,res) => {})
app.use(cors())
app.get('/api/get', (req, res) => {})
实现 JSONP 接口的步骤
- 获取客户端发过来的回调函数的名字
- 得到要通过 JSONP 形式发送给客户端的数据
- 根据前两步得到的数据,拼接出一个函数调用的字符串
- 把上一步得到的字符串,响应给客户端的
app.get('/api/jsonp', (req, res) => {
const funcName = req.query.callback
const data = {
name:'zs',
age:22
}
const scriptStr = `${funcName}(${JSON.stringify(data)})`
res.send(scriptStr)
})
$('#btn').on('click', function() {
$.ajax({
method:'GET',
url:'http://127.0.0.1/api/jsonp',
dataType:'jsonp',
success: function(res){
console.log(res)
}
})
})
七、前后端身份认证
1. Web 开发模式
目前主流的 Web 开发模式共有两种,分别是:
- 基于服务端渲染的传统 Web 开发模式
- 基于前后端分离的新型 Web 开发模式
服务器渲染的 Web 开发模式
服务器渲染的概念:服务器发送给客户端的 HTML 页面,是在服务器通过字符串拼接,动态生成的。因此,客户端不需要使用 Ajax 这样的技术额外请求页面的数据。
app.get('/index.html', (req, res) => {
const user = {name:'zs', age:20}
const html = `<h1>姓名:${user.name}, 年龄:${user.age}</h1>`
})
服务端渲染的优缺点
优点:1. 前端耗时时间端。2. 有利于 SEO(爬虫)
缺点:1. 占用服务器资源。2. 不利于前后端分离,开发效率低
前后端分离的 Web 开发模式
前后端分离的概念:前后端分离的开发模式,依赖于 Ajax 技术的广泛应用。简而言之,前后端分离的 Web 开发模式,就是后端只负责提供 API 接口,前端使用 Ajax 调用接口的开发模式
前后端分离的优缺点
优点:1. 开发体验好。2. 用户体验好。3. 减轻了服务器的渲染压力。
缺点:不利于 SEO
如何选择
看需求!
2. 身份认证
Ⅰ、什么是身份认证
就是通过一些手段,对用户的身份进行确认。
Ⅱ、为什么需要身份认证
是为了确认当前所声称为某种身份的用户,确实是所声称的用户。
Ⅲ、不同开发模式下的身份认证
- 服务端渲染推荐使用 Session 认证机制
- 前后端分离推荐使用 JWT 认证机制
Ⅳ、Session 认证机制
HTTP 协议的无状态性
HTTP 协议的无状态性:客户端的每次 HTTP 请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次 HTTP 请求的状态。
如何突破 HTTP 无状态的限制
使用认证机制。相当于客户端在服务端留一个标记。
什么是 Cookie
Cookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成。
不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器。
Cookie 的几大特性:1. 自动发送。2. 域名独立。3. 过期时限。4. 4KB 限制
Cookie 在身份认证中的作用
客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动将 Cookie 保存在浏览器中。
随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给服务器,服务器即可验明客户端的身份。

Cookie 不具有安全性
由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器。
提高身份认证的安全性
出示 Cookie 外加上客户端还要去匹配。
Session 的工作原理

Ⅴ、在Express 中使用 Session 认证
安装 express-session 中间件
npm install express-session
配置 express-session 中间件
var session = require('express-session')
app.use(session({
secret:'keyboard cat', // secret 属性的值可以是任意字符串
resave:false, // 固定写法
saveUninitialized:true // 固定写法
}))
向 session 中存数据
配置完成之后,就可以通过 req.session 来访问和使用 session 对象了。从而存储用户的关键信息。
app.post('/api/login', (req, res) => {
if(req.body.username !== 'admin' || req.body.password !== '000000'){
retrun res.send({ status: 1, msg: '登录失败'})
}
req.session.user = req.body // 将用户的信息,存储到 session 中
req.session.islogin = true // 将用户的登录状态,存储到 session 中
res.send({ status: 0, msg: '登录成功'})
})
从 session 中取数据
app.get('/api/username', (req, res) => {
if(!req.session.islogin){
return res.send({ status: 1, msg: 'fail'})
}
res.send({ status: 0, msg: 'success', username: req.session.user.username})
})
清空 session
调用 req.session.destroy() 函数,即可清空服务器保存的 session 信息
app.post('/api/login', (req, res) => {
// 清空当前客户端对应的 session 信息
req.session.destroy()
res.send({
status: 0,
msg: '退出登录成功'
})
})
Ⅵ、 JWT 认证机制
了解 Session 认证的局限性
Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问。只有配置前端跨域请求之后才能用 Seesion 认证。这需要很多额外的配置才可以。
注意:
- 当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制。
- 当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用 JWT 认证机制
什么是 JWT
JWT:目前最流行的跨域认证解决方案
JWT 的工作原理

用户的信息通过 Token 字符串的形式,保存在客户端浏览器中,服务器通过还原 Token 字符串的形式来认证用户的身份。
JWT 的组成部分
JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。
三者之间使用英文的 “.” 分隔,格式如下:

JWT 的三个部分各自代表的含义
Payload 部分才是真正的用户信息,是通过加密之后生成的字符串。
Header 和 Signature 是安全性相关的部分,只是为了保证 Token 的安全性。

JWT 的使用方式
客户端收到服务器的 JWT 之后,通常会将它存储在 localStorage 或 sessionStorage 中。
客户端每次与服务器通信,都要带上这个JWT 的字符串,从而进行身份认证。推荐的做法是把 JWT放在HTTP请求头的Authorization 字段中:
Authorization: Bearer <token>
在 Express 中使用 JWT
安装 JWT 相关的包
npm install jsonwebtoken
npm install express-jwt
其中:
- jsonwebtoken 用于生成 JWT 字符串
- express-jwt 用于 JWT 字符串解析还原成 JSON 对象
导入 JWT 相关的包
使用 require() 函数,分别导入 JWT 相关的两个包:
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
定义 secret 密钥
为了保证 JWT 字符串的安全性,防止 JWT 字符串在网络传输过程中被别人破解,我们需要专门定义一个用于加密和解密的 secret 密钥:
-
当生成 JWT 字符串的时候,需要使用 secret 密钥对用户的信息进行加密,最终得到加密好的 JWT 字符串
-
当把 JWT 字符串解析还原成 JSON 对象的时候,需要使用 secret 密钥进行解密
const secretKey = "woshiwangzijuntadie"
在登录成功后生成 JWT 字符串
调用 jsonwebtoken 包提供的 sig() 方法,将用户的信息加密成 JWT 字符串,响应给客户端:
app.post('/api/login', function(req, res){
res.send({
status: 200,
message: '登录成功',
taken: jwt.sign({ username:userinfo.username}, secretKey, { expiresIn: '30s'})
})
})
将 JWT 字符串还原为 JSON 对象
客户端每次在访问那些有权限接口的时候,都需要主动通过请求头中的 Authorization 字段,将 Token 字符串发送到服务器进行身份认证。
此时,服务器可以通过 express-jwt 这个中间件,自动将客户端发送过来的 Token 解析还原成 JSON 对象:
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/ai\//] }))
使用 req.user 获取用户信息
当 express-jwt 这个中间件配置成功之后,即可在那些有权限的接口中,使用 req.user 对象,来访问从 JWT 字符串中解析出来的用户信息了,示例代码如下:
app.get('/admin/getinfo', function(req, res){
console.log(req.user)
res.send({
status: 200,
message: '获取用户信息成功',
data: req.user
})
})
捕获解析 JWT 失败后产生的错误
当使用 express-jwt 解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行。我们可以通过 Express 的错误中间件,捕获这个错误并进行相关的处理,示例代码如下:
app.use((err, req, res, next) => {
if(err.name === 'UnauthorizedError' ){
return res.send({
status: 401,
message: '无效的token'
})
}
res.send({
status: 500,
message: '未知的错误'
})
})

12万+

被折叠的 条评论
为什么被折叠?



