1.Node.js 概述
1.1 Node.js 不是一门编程语言,它是一个执行 JavaScript 代码的工具。工具是指可以安装在计算机操作系统之上的软件。
1.2 浏览器和 Node.js 都可以运行 JavaScript ,因为它们都内置了 JavaScript V8 Engine ,它可以将 JavaScript 代码编译为计算机能够识别的机器码。
1.3 浏览器中运行的 JavaScript 和 node.js 中运行的 JavaScript:
- 在内置了 JavaScript V8 Engine 以后实际上只能执行 ECMAScript ,就是语言中的语法部分;
- 浏览器为了能够让 JavaScript 操作浏览器窗口以及 HTML 文档,所以在 JavaScript V8 Engine 中添加了控制它们的 API ,就是 DOM 和 BOM ,所以JavaScript 在浏览器中运行时是可以控制浏览器窗口对象和 DOM 文档对象的;
- 和浏览器不同,在 Node.js 中是没有 DOM 和 BOM 的,所以在 Node.js 中不能执行和它们相关的代码,比如 window.alert() 或者 document.getElementbyId() 。DOM 和 BOM 是浏览器环境中特有的。
- 在 Node.js 中,作者向其添加了很多系统级别的 API ,比如对操作系统中的文件和文件夹进行操作。获取操作系统信息,比如系统内存总量是多少,系统临时目录在哪,对系统的进程操作等等。
- JavaScript 运行在浏览器中控制的是浏览器窗口和 DOM 文档。
- JavaScript 运行在 Node.js 中控制的操作系统级别的内容。
- 浏览器中的 JavaScript 不能控制系统级别的 API:浏览器是运行在用户的操作系统中的,如果能控控制系统级别的 API 就会存在安全的问题; Node.js 是运行在远程的服务器中的,访问的是服务器系统 API,不存在这方面的的安全问题。
- Node.js 能够做的:我们通常使用它来构建服务器端应用和创建前端工程化工具;
- JavaScript 运行在浏览器中我们就叫它客户端 JavaScript;
- JavaScript 运行在 Node.js 中我们就叫它服务器端 JavaScript
2、系统环境变量
- 系统环境变量是指在操作系统级别上定义的变量,变量中储存了程序运行时所需要的参数
- 比如在使用 webpack 构建前端应用时就使用到了系统环境变量,因为 webpack 需要根据系统环境变量判断当前为开发环境还是生产环境,根据环境决定如何构建应用
在开发环境的操作系统中定义 NODE——ENV 变量,值为 development ,在生产环境的操作系统中定义 NODE_EVN 变量,值为 production 。 - webpack 在运行时通过 process.env.NODE_EVN 获取变量的值,从而得出当前代码的运行环境是什么。
- 环境变量 PATH:系统环境变量 PATH 中存储的都是应用程序路径。当要求系统运行某一个应用程序又没有告诉它完整程序路径时,此时操作系统会先在当前文件夹中查找应用程序,如果查找不到就会去系统环境变量 PATH 中指定的路径中查找。
3、安装 Node.js
- 官网下载: https://nodejs.org/zh-cn/
- 有两个版本。LTS:长期支持版(稳定版)可以运行在生产环境中;Current: 最新版(预览版)不建议运行在生产环境中,因为可能有 BUG
- 安装过程中可能出现的两个问题:
在运行 node 命令时提示“不是内部或外部命令,也不是可运行的程序或批处理文件”:
将 Node 应用程序目录添加到系统环境变量中,然后重新启动命令行工具再次执行 node 命令 - 安装 Node 的过程中出现代码为 2502 和 2503 的错误
1.通过管理员权限打开命令行工具;
2.切换到 node 安装包所在的目录;
3.通过 mesiexec/package node-v10.15.0-x64.mis 运行 node 应用程序安装包(node-v10.15.0-x64.mis 安装包的名字)
4、全局变量
- 在 Node.js 环境中是没有 window 的,所以 window 对象自然是未定义的
- 在 Node.js 环境中全局对象为 global ,在 global 对象中会存在一些和 window 对象名字相同且作用相同的方法
- 在 Node.js 环境中声明的变量不会被添加到全局对象中,变量声明后只能在当前文件中使用
- process 对象是一个全局变量,提供了有关当前 Node.js 进程的信息并对其进行控制。
// process是全局变量,使用时,无需引入,引入也不报错,但是没有必须引入
// const process= require("process");
console.log(process)
//输出当前版本
console.log(process.version) //v14.16.0
//输出操作系统架构
console.log(process.arch) // x64
//输出操作系统平台
console.log(process.platform) // win32
//环境变量
console.log(process.env)
//自定义环境变量
process.env.NODE_ENV = 'develop'
console.log(process.env.NODE_ENV) // develop
//获取进程编号
console.log(process.pid) //(系统分配,每次执行不一样)
//杀死进程
process.kill() //参数:进程编号
5、模块成员导出与导入
- 模块概述:在 Node.js 环境中,默认就支持模块系统,该模块系统遵循 CommonJS 规范;一个 JavaScript 文件就是一个模块,在模块文件中定义的变量和函数默认只能在模块文件内部使用,如果需要在其他文件中使用,必须显式声明将其进行导出
- 模块成员导出:
在每一个模块文件中,都会存在一个 module 对象,即模块对象。在模块对象中保存了和当前模块相关信息;
在模块对象中有一个属性 exports ,它的值是一个对象,模块内部需要被导出的成员都应该存储到这个对象中 - 模块成员导入:
在其他文件中通过 require 方法引入模块,require 方法的返回值就是对应模块的 module.exports 对象 - 在导入模块时,模块文件后缀 .js 可以省略,文件路径不可省略
- require 方法属于同步导入模块,模块导入后可以立即使用
- 通过 require 方法引入模块时会执行该模块中的代码
- 在导入其他模块时,建议使用 const 关键字声明常量,防止模块被重置
- 有时在一个模块中只会导出一个成员,为方便其他模块使用,导出时可以直接把值赋给 module.exports;导入直接获取,不需要打点调用了
6、 Module Wrapper Function 模块包装函数
- 在模块文件执行之前,模块文件中的代码会被包裹在模块包装函数中,这样每个模块文件中的代码就都拥有了自己的作用域,所以在模块外部就不能访问模块内部的成员了。
(function (exports, require, module, __filename, __dirname) {
// entire module code lives here
})() - module 和 require 是模块内部成员,不是全局对象 global 下面的属性
__filename : 当前模块文件名称
__dirname :当前文件所在路径 - exports: 引用地址指向了 module.exports 对象的简写形式
- 在导入模块时最终导入的是 module.exports 对象,所以在使用 exports 对象添加导出成员时不能修改引用地址
7、Node.js 内置模块
在 Node.js 安装完成后,会内置一些非常有用的模块
Path: 模块内提供了一些路径操作相关的方法
// 引入
const path = require("path")
//获取当前文件所在的路径
console.log(process.cwd())
console.log(__dirname)
//获取当前文件所在的路径
console.log(process.cwd())
//获取当前文件所在的路径
// dir = directory 目录 C:\Users\ROG\Desktop\拉钩教育\fed-e-task-base-03-01\code
console.log(__dirname)
//获取当前文件的完整路径 C:\Users\ROG\Desktop\拉钩教育\fed-e-task-base-03-01\code
console.log(__filename)
//获取文件的扩展名 .js
console.log(path.extname(__filename))
// 获取路径中的目录部分 C:\Users\ROG\Desktop\拉钩教育\fed-e-task-base-03-01\code
console.log(path.dirname(__filename))
//获取路径中的文件名 path.js
console.log(path.basename(__filename));
// 相当于../
const t = path.join(__dirname, '..')
console.log(t) // C:\Users\ROG\Desktop\拉钩教育\fed-e-task-base-03-01
//将多个路径合并起来
const a = path.join("d:/",'a', 'b')
console.log(a) // d:\a\b
fs: 文件操作系统,提供了操作文件的方法
// 文件写入
const fs = require("fs");
fs.writeFile('./1.txt','我是写入的内容', (err)=>{
if(err){
throw err;
}
console.log("写入成功")
});
// 文件读取
var flieName = path.join(__dirname, './1.txt');
fs.readFile(flieName, (err, data)=>{
if(err) throw err;
//data是二进制数据,但是以16进制数据输出
console.log(data);
console.log(data.toString()) // 我是写入的内容
})
// 文件删除
var flieName = path.join(__dirname, './1.txt');
fs.unlink(flieName, (err)=>{
if(err) throw err;
console.log("刪除成功")
})
//追加写入
fs.appendFile(__dirname + '/2.txt', '曾经有一首歌是这样唱的', err => {
if(err) throw err;
console.log("追加成功")
})
//创建目录
fs.mkdir('./a', err => {
if(err){
throw err;
}
console.log("success");
})
// 删除目录 rmdir 只能删除空目录
// 先删除目录下的普通文件,再通过rmdir删除空目录
fs.rmdir('./a', err => {
if (err) {
throw err;
}
console.log("删除成功");
})
//重命名
fs.rename(__dirname + "/a1", __dirname + "/a11", err => {
if (err) {
throw err;
}
console.log("重命名成功");
});
// 写文件
fs.readdir(__dirname, (err, data) => {
if(err) throw err;
console.log(data.length); //文件夹和文件都可以读取出来
data.map((d)=>{
console.log(d);
fs.stat(__dirname + '/' + d, (err, stat) => {
if(err) throw err;
if(stat.isDirectory()){ //判断是否是目录
console.log("目录:", d);
}else if(stat.isFile()){
console.log("文件:", d);
}
})
})
- http
const http = require("http")
/**
* 创建服务器
*/
const server = http.createServer((req, res) => {
res.statusCode = 200
res.setHeader("Content-type", "text/plain;charset=utf-8")
res.end("你好,node.js")
})
/**
* 发布web服务
*/
const port = 3000;
const host = "localhost"
server.listen(port, host, ()=>{
console.log(`服务器运行在htp://${host}:${port}`)
})
在引入内置模块时,使用的是模块的名字,前面不需要加任何路径
8、npm 概述
- Node.js 软件包: 每一个基于 Node.js 平台开发的应用程序都是 Node.js 软件包;
- 所有 Node.js 软件包都被托管在 www.npmjs.com 中
- npm:Node Package Manager , Node.js 环境中的软件包管理器,随 Node.js 一起被安装;
- 它可以将 Node 软件包添加到我们的应用程序中并对齐进行管理,比如下载,删除,更新,查看版本等等;
- 它没有用户界面,需要在命令行工具中通过命令的方式使用,对应的命令就是 npm ;
- NPM 和 Node 是两个独立的应用程序,只是被捆绑安装了,可以通过版本号证明
9、package.json
- Node.js 规定在每一个软件包中都必须包含一个叫做 package.json 的文件;
- 它是应用程序的描述文件,包含的应用程序相关的信息,比如应用名称,应用版本,应用作者等等;
- 通过 package.json 文件可以方便管理应用和发布应用; 创建 package.json 文件: npm init ;
- 快速创建 package.json 文件: npm init --yes
10、下载 Node.js 软件包
- 在应用程序的根目录执行命令, npm install <pkg> 或者 npm i <pkg> 如:npm install lodash
- 软件包下载完成后会发生三件事:
1.软件包会被存储在 node_modules 文件夹中,如果在应用中不存在此文件夹, npm 会自动创建
2.软件包会被记录在 package.json 文件中,包含软件包的名字以及版本号
3.npm 会在应用中创建 package-lock.json 文件,用于记录元件包及软件包的依赖包的下载地址及版本
11、使用 Node.js 软件包
- 在引入第三方软件包时,在 require 方法中不需要加入路径信息,只需要使用软件包的名字即可,require 方法会自动去 node_modules 文件夹中进行查找
- 软件包依赖问题说明:
1. 比如在我的应用中要依赖 mongoose 软件包,于是我下载了它,但是在 node_modules 文件夹中除了包含 mongoose 以外还多出了很多其他软件包,为什么会多出这么多软件包呢?
实际上它们又是 mongoose 依赖的软件包。
2. 为什么 mongoose 依赖的软件包不放在 mongoose 文件夹中呢?
在早期的 npm 版本中, 某个软件包依赖的其他软件包都会被放置在该软件包内部的node_modules 文件夹中,但是这样做存在两个问题,第一个问题是很多软件包都会有相同的依赖,导致开发者在一个项目中会下载很多重复的软件包,比如 A 依赖 X,B 依赖 X,C 依赖 X,在这种情况下 X 就会被重复下载三次。第二个问题是文件夹嵌套层次太深,导致文件夹在 windows系统中不能被直接删除。比如 A 依赖 B, B 依赖 C, C 依赖 D ... , 就会发生文件夹依次嵌套的情况。
3. 所有的软件包都放置在 node_modules 文件夹中不会导致软件包的版本冲突吗?
在目前的 npm 版本中,所有的软件包都会被直接放置在应用根目录的 node_modules 文件夹中,这样虽然解决了文件夹嵌套层次过深和重复下载软件包的问题,但如果只这样做肯定会导致软件包版本冲突的问题,如何解决呢?
比如 A 依赖 X 的 1 版本,B 依赖 X 的 2 版本,如果你先下载的是 A,那么 A 依赖的 X 会被放置在根目录的 node_modules 文件夹中, 当下载 B 时,由于在根目录中已经存在 X 并且版本不一致,那么 B 依赖的 X 就会被放置在 B 软件包中的 node_module 文件夹中,通过此方式解决软件包版本冲突的问题。
4. node_modules 文件夹中的软件包都需要提交到 git 仓库中吗?
在 node_modules 文件夹中有很多软件包,随着应用程序的增长,软件包也会越来越多,甚至会达到几百兆。当我们将应用提交到版本库时,我们不想提交它,因为它们不是我们应用中的源代码,而且由于碎文件比较多,其他人在检出代码时需要等待的时间会很久。当其他人拿到应用程序时没有依赖软件包应用程序是运行不起来的,如何解决呢?
实际上应用程序依赖了哪些软件包在 package.json 文件中都会有记录,其他人可以通过 npm install 命令重新下载它们。为了保持下载版本一直,npm 还会根据 package-lock.json 文件中的记录的地址进行下载。将应用程序提交到版本库之前,将 node_modules 文件夹添加到 .gitignore 文件中。
12、语义版本控制
1.版本号规范: 2.3.4
2:添加新功能(破坏现有API)==> 2.2.4
3:添加新功能(不会破坏现有 API,在现有API 的基础上进行添加) ==> 2.4.4
4:用于修复 bug ==> 2.3.5
2.版本号更新规范
^2.3.4:主要版本不变,更新次要版本和补丁版本
~2.3.4:主要版本和次要版本不变,更新补丁版本
2.3.4:使用确切版本,即主要版本、次要版本、补丁版本固定
13、查看软件包实际版本
- 在 node_module 文件夹中找到对应的依赖软件包,找到它的package.json文件,可以在这个文件中的 version 字段中找到它的具体版本
- 通过 npm list 命令查看所有依赖软件包的具体版本,--depth 选项指定查看依赖包的层级
14、查看软件包的元数据
- npm view mongoose
- npm view mongoose versions 查看具体的某一项的元数据
- npm view mongoose dist-tags dependencies 查看多个项,空格分开
15、下载特定版本的软件包
- npm i <pkg>@<version> npm i mongoose@2.4.2 lodash@4.7.0
- cat package.json npm list --depth 0
- 删除软件包
npm uninstall <pkg> npm uninstall mongoose npm un mongoose
16、更新软件包
- 通过 npm outdated 命令可以查看哪些软件包已经过期,对应的新版本是什么
- 通过 npm update 更新过期的软件包,更新操作遵循语义定义版本控制规则
17、项目依赖和开发依赖
- 项目依赖:无论在开发环境还是线上环境只要程序在运行的过程中需要使用的软件包就是项目依赖。比如 lodash, mongoose
- 开发依赖:在应用开发阶段使用,在生产环境中不需要使用的软件包,比如 TypeScript 中的类型声明文件
- 在 package.json 文件中,项目依赖和开发依赖要分别记录,项目依赖被记录在 dependencies 对象中,开发依赖都被记录在 devDependencies 中,使开发者可以在不同的环境中下载不同的依赖软件包
- 在下载开发依赖时,要在命令的后面加上 --save-dev 选项或者 -D 选项。 npm i eslint -D
- 在开发环境中下载的所有依赖软件包 : npm install
- 在生产环境中只下载项目依赖软件包: npm install --prod
18、全局安装与本地安装
- 本地安装:将软件包下载到应用根目录下的 node_modules 文件夹中,软件包只能在当前应用中使用
- 全局安装:将软件包下载到操作系统的指定目录中,可以在任何应用中使用
- 通过 -g 选项将软件包安装到全局: npm install <pkg> -g
- 查看全局软件包安装位置: npm root -g
- 删除全局中的软件包: npm un npm-check-updates -g
- 查看全局中安装了哪些软件包: npm list -g --depth 0
- 查看全局中有哪些过期软件包: npm outdated -g
- nodemon :命令工具软件包,可以监控文件变化,自动重新执行文件 npm install nodemon@2.0.7 -g
19、npm-check-updates 强制更新
- npm-check-updates 可以查看应用中有哪些软件包过期,可以强制更新 package.json 文件中软件包版本
1.将 npm-check-updates 安装到全局: npm install npm-check-undates -g
2.查看过期软件包: npm-check-updates
3.更新: package.json : ncu -u
4.安装软件包: npm i
5.检测: npm outdated 或 npm-check-updates
20、发布软件包
- 注册 npm 账号 https://www.npmjs.com/ yao_xiao_yao
- 创建软件包 npm init -y 生成package.json
- 创建模块
- 登录 npm (npm 镜像地址必须为 npmjs.com) npm login
- 发布软件包 npm publish
- 测试:在其他应用中使用该软件包; npm install 包名
21、更新版本号
- 在软件包的源代码发生更改后,是不能直接发布的,应该更新软件包的版本号再进行发布
- 更新主要版本号: npm version major
- 更新次要版本号: npm version minor
- 更新补丁版本号: npm verison patch
22、撤销已发布的软件包
- 只有在发布软件包的24小时内才允许撤销
- 软件包撤销后 24 小时以后才能重新发布
- 重新发布时需要修改包名称和版本号
npm unpublish <pkg> --force
23、更改 npm 镜像地址
- 由于 npmjs.com 是国外的网站,大多数的时候下载软件包的速度比较慢,可以通过配置的方法更改 npm 工具的下载地址
- 获取 npm 配置 npm config list -l --json
-l 列表表示所有默认配置选项
--json 以 json 格式显示配置选项 - 设置 npm 配置
获取 npm 下载地址: npm config get registry
获取 npm 用户配置文件: npm config get userconfig - 更改 npm 镜像地址
npm config set registry https://registry.npm.taobao.org
npm config set registry https://registry.npmjs.org
24、 npx 命令
- npx 是 npm 软件包提供的命令,它是 Node.js 平台下软件包执行器。主要用途有两个,第一个是临时安装软件包执行后删除它,第二个是执行本地安装的提供命令的软件包
1.临时安装软件包执行后删除软件包
有些提供命令的软件包使用的频率并不高,比如 creat-react-app 脚手架工具,我们不能临时下载使用,然后删除掉它。
npx create-react-app react-test
2.执行本地安装的软件包
现在有两个项目都依赖了某个命令工具软件包,但是项目 A 依赖的是它的1版本,项目 B 版本依赖的是它的 2 版本,该软件包可以在本地进行安装,在 A 项目中安装它的 1 版本,在 B 项目中安装它的 2 版本,在应用中可以通过 npx 调用 node_modules 文件夹中安装的命令工具
将所有软件包安装到应用本地是现在最推荐的做法,一是可以防止软件包的版本冲突问题,二是其他开发者在恢复应用依赖时可以回复全部依赖,因为软件包安装到本地后会被 package.json 文件记录,其他开发者在运行项目时不会因为缺少依赖而报错
25、配置入口文件的作用
- 应用程序入口文件就是应用程序执行的起点,就是启动应用程序时执行的文件
场景一:其他开发者拿到你的软件包之后,通过该文件可以直到应用的入口文件是谁,通过入口文件启动应用
场景二:通过 node 应用文件夹 命令启动应用。 node 命令执行 package.json 文件中 main 选项指定的入口文件,如果没有指定的入口文件,则执行 index.js
26、模块查找规则
- 在指定了查找路径的情况下: require("./server")
查找 server.js ; 查找 server.json ; 查找 server 文件夹,查看入口文件(package.json -> main) ; 查找 server 文件夹 中的 index.js 文件 - 在没有指定查找路径的情况下: require("server")
27、异步编程
- CPU 与 存储器
- CPU 中央处理器,计算机核心部件,负责运算和指令调用;开发者编写的
- JavaScript 代码在被编译为机器码以后就是通过CPU执行的
存储器:
内存 用于临时存储数据,断电后数据丢失,由于数据读写速度快,计算机中的应用都是在内存中运行的;
磁盘 用于持久存储数据,断电后数据不丢失,内部有磁头依靠马达转动在盘片上读写数据,速度比内存慢 - 计算机应用程序在没有运行时是存储在磁盘中的,当我们启动应用程序后,应用程序会被加载到内存中运行,应用程序中的指令会被中央处理器CPU来执行
28、I/O
- I 就是 Input 表示输入, O 就是 Output 表示输出, I/O操作就是输入输出操作
- 比如数据库的读写操作就是I/O操作,因为数据库文件是存储在磁盘中的,而我们编写的程序是运行在内存中的,将内存中的数据写入数据库对于内存来说就是输出,查询数据库中的数据就是将磁盘中的数据读取到内存中,对如内存来说就是输入
- I/O模型
1. CPU 等待I/O操作完成获取到操作结构后再去执行其他命令,这是同步I/O操作(阻塞I/O)
2. CPU 不等待I/O操作完成, CPU 在发出I/O 指令后,内存和磁盘开始工作,CPU继续执行其他命令。当I/O操作完成后再通知 CPU I/O操作的结果是什么。这是异步I/O操作(非阻塞I/O) - 同步I/O和异步I/O区别就是是否等待I/O结果
29、进程和线程
- 每当我们运行应用程序中,操作系统会创建该应用程序的实例对象,该实例对象就是应用程序的进程,操作系统会操作系统会按照进程为单位为应用程序分配资源,比如内存,这样程序才能够在计算机的操作系统中运行起来。
- 线程被包裹在进程之中,是进程中的实际运作单位,一条线程指的就是进程中的一个单一顺序的控制流。也就是说,应用程序要做的事情都存储在线程之中。可以这样认为,一条线程就是一个待办列表,供 CPU 执行。
30、回调函数
- 回调函数是指通过函数参数的方式将一个函数传递到另一个函数中,参数函数就是回调函数
- 在主函数中调用回调函数时,可以为回调函数传递参数
- 在异步编程中,异步 API 执行的结果就是通过回调函数传递参数的方式传递到上层代码中的。
- 回调地狱:回调地狱是回调函数多层嵌套导致代码难以维护的问题。
基于回调函数的异步编程一不小心就会产生回调地狱的问题。
31、基于 Promise 的异步编程
- Promise 是 JavaScript 中异步编程解决方案,可以解决回调函数方案中的回调地狱问题。可以将 Promise 理解为容器,用于包裹异步 API 的容器,当容器中的异步 API 执行完成后,Promise 允许我们在容器的外面获取异步 API 的执行结果,从而避免回调函数嵌套。
- Promise 翻译为承若,表示它承若帮我们做一些事情,既然它承若了它就要去做,做就会有一个过程,就会有一个结果,结果要么是成功要么是失败。
- 所以在 Promise 中有三种状态, 分别为等待(pending),成功(fulfilled),失败(rejected)。
- 默认状态为等待,等待可以变为成功,等待可以变为失败。
- 状态一旦更改不可改变,成功不能变回等待,失败不能变回等待,成功不能变成失败,失败不能变成成功。
- Promise 基础语法
const promise = new Promise(function (resolve, reject) {
fs.readFile("./x.txt", "utf-8", function(error, data) {
if (error) {
// 将状态从等待变为失败
reject(error)
} else {
// 将状态从等待变为成功
resolve(data)
})
});
promise.then(function (data) {
console.log(data)
}).catch(function (error) {
console.log(error)
})
Promise 链式调用
Promise.all 并发异步操作
const fs = require("fs")
Promise.all([
readFile("./x.txt"),
readFile("./y.txt"),
readFile("./z.txt")
]).then(function (data) {
console.log(data)
})
基于异步函数的异步编程
Promise 虽然解决了回调地狱的问题,但是代码看起来仍然不简洁。
使用异步函数简化代码提高异步编程体验。
##### 1. 异步函数概述
function readFile(path) {
return new Promise(function (resolve, reject) {
fs.readFile(path, "utf-8", function (error, data) {
if (error) return reject(error)
resolve(data)
})
})
}
async function getFileContent() {
let x = await readFile("./x.txt")
let y = await readFile("./y.txt")
let z = await readFile("./z.txt")
return [x, y, z]
}
getFileContent().then(console.log)
- async 声明异步函数的关键字,异步函数的返回值会被自动填充到 Promise 对象中。
- await 关键字后面只能放置返回 Promise 对象的 API。
- await 关键字可以暂停函数执行,等待 Promise 执行完后返回执行结果。
- await 关键字只能出现在异步函数中。
- await 关键字后面只能放置返回 Promise 对象的 API。
##### 2. util.promisify
在 Node.js 平台下,所有异步方法使用的都是基于回调函数的异步编程。为了使用异步函数提高异步编程体验,可以使用 util 模块下面的 promisify 方法将基于回调函数的异步 API 转换成返回Promise 的API。
const util = require("util")
const readFile = util.promisify(fs.readFile)
async function getFileContent() {
let x = await readFile("./x.txt", "utf-8")
let y = await readFile("./y.txt", "utf-8")
let z = await readFile("./z.txt", "utf-8")
return [x, y, z]
}
getFileContent().then(console.log)
32、事件循环机制 Event Loop 机制概述
- 事件循环机制用于管理异步 API 的回调函数什么时候回到主线程中执行。
- Node.js 采用的是异步 I/O 模型。同步 API 在主线程中执行,异步 API 在底层的 C++ 维护的线程中执行,异步 API 的回调函数在主线程中执行。在 JavaScript 应用运行时,众多异步 API 的回调函数什么时候能回到主线程中调用呢?这就是事件循环机制做的事情,管理异步 API 的回调函数什么时候回到主线程中执行。
- 因为 Node.js 是事件驱动的。事件驱动就是当什么时候做什么事情,做的事情就定义在回调函数中,可以将异步 API 的回调函数理解为事件处理函数,所以管理异步API回调函数什么时候回到主线程中调用的机制叫做事件循环机制。
33、 Event Loop 的六个阶段
事件循环是一个循环体,在循环体中有六个阶段,在每个阶段中,都有一个事件队列,不同的事件队列存储了不同类型的异步API 的回调函数。
- Timers:用于存储定时器的回调函数(setInterval, setTimeout)。
- Pending callbacks:执行与操作系统相关的回调函数,比如启动服务器端应用时监听端口操作的回调函数就在这里调用。
- Idle, prepare:系统内部使用。
- IO Poll:存储 I/O 操作的回调函数队列,比如文件读写操作的回调函数。如果事件队列中有回调函数,执行它们直到清空队列。否则事件循环将在此阶段停留一段时间以等待新的回调函数进入,这个等待取决于以下两个条件:
1. setImmediate 队列(check 阶段)中存在要执行的回调函数.
2. timers 队列中存在要执行的回调函数. 在这种情况下, 事件循环将移至 check 阶段, 然后移至Closing callbacks 阶段, 并最终从 timers 阶段进入下一次循环。 - Check:存储 setImmediate API 的回调函数。
- Closing callbacks:执行与关闭事件相关的回调,例如关闭数据库连接的回调函数等。
循环体会不断运行以检测是否存在没有调用的回调函数,事件循环机制会按照先进先出的方式执行他们直到队列为空。
34、宏任务与微任务(异步API的类型)
- 宏任务:setInterval, setTimeout, setImmediate, I/O
- 微任务:Promise.then Promise.catch Promise.finally, process.nextTick
- 微任务与宏任务的区别
1. 微任务的回调函数被放置在微任务队列中,宏任务的回调函数被放置在宏任务队列中。
2. 微任务优先级高于宏任务。
当微任务事件队列中存在可以执行的回调函数时,事件循环在执行完当前阶段的回调函数后会暂停进入事件循环的下一个阶段,事件循环会立即进入微任务的事件队列中开始执行回调函数,当微任务队列中的回调函数执行完成后,事件循环再进入到下一个阶段开始执行回调函数。
nextTick 的优先级高于 microTask,在执行任务时,只有 nextTick 中的所有回调函数执行完成后才会开始执行 microTask。
不同阶段的宏任务的回调函数被放置在了不同的宏任务队列中,宏任务与宏任务之间没有优先级的概念,他们的执行顺序是按照事件循环的阶段顺序进行的。
35、 Event Loop 代码解析
- 在 Node 应用程序启动后,并不会立即进入事件循环,而是先执行输入代码,从上到下开始执行,同步API 立即执行,异步 API 交给 C++ 维护的线程执行,异步 API 的回调函数被注册到对应的事件队列中。当所有输入代码执行完成后,开始进入事件循环。
36、process.nextTick()
- 此方法的回调函数优先级最高,会在事件循环之前被调用。
- 如果你希望异步任务尽可能早地执行,那就使用 process.nextTick。
37、setImmediate()
- setImmediate 表示立即执行,它是宏任务,回调函数会被会放置在事件循环的 check 阶段。
- 在应用中如果有大量的计算型任务,它是不适合放在主线程中执行的,因为计算任务会阻塞主线程,主线程一旦被阻塞,其他任务就需要等待,所以这种类型的任务最好交给由 C++ 维护的线程去执行。
- 可以通过 setImmediate 方法将任务放入事件循环中的 check 阶段,因为代码在这个阶段执行不会阻塞主线程,也不会阻塞事件循环。
- Node 适合 I/O 密集型任务,不适合 CPU 密集型任务,因为主线程一旦阻塞,程序就卡主了。
38、web 网站的组成
- 从开发者的角度来看,web 应用主要由三部分组成:用户界面,业务逻辑,数据。
- 用户界面 (视图层):用于将数据展示给用户的地方,采用 HTML,CSS,JavaScript 编写。
- 业务逻辑 (控制层):实现业务需求和控制业务流程的地方,可以采用 Java, PHP, Python, JavaScript编写。
- 数据 (模型层):应用的核心部分, 应用业务逻辑的实现,用户界面的展示都是基于数据的, web 应用中的数据通常是存储在数据库中的,数据库可以采用 MySql, Mongodb 等。
39、什么是 web 服务器
- 服务器是指能够向外部(局域网或者万维网)提供服务的机器(计算机)就是服务器。
- 在硬件层面,web 服务器就是能够向外部提供网站访问服务的计算机。
- 在这台计算机中存储了网站运行所必须的代码文件和资源文件。
- 在软件层面,web 服务器控制着用户如何访问网站中的资源文件,控制着用户如何与网站进行交互。
- 客户端:web 应用中的客户端是指用户界面的载体,实际上就是浏览器。用户可以通过浏览器这个客户端访问网站应用的界面,通过用户界面与网站应用进行交互。
- 网站的运行:web 应用是基于请求和响应模型的。
40、IP和域名
- IP:Internet Protocol address:互联网协议地址,标识网络中设备的地址,具有唯一性。例如:45.113.192.101
- 域名 (Domain Name):是由一串用点分隔的字符组成的互联网上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位 (摘自维基百科)。
41、DNS 服务器
- Domain Name Server:域名服务器,互联网域名解析系统,它可以将"人类可识别"的标识符映射为系统内部通常为数字形式的标识码。(摘自维基百科)
42、端口
- 端口:是设备与外界通讯交流的出口,此处特指计算机中的虚拟端口。0 ~ 65535
- 比如在一座大厦当中有很多房间,每间房间都提供着不同的服务,我们可以通过房间号找到提供不同服务的房间。
- 服务器就是这座大厦,在服务器中可以提供很多服务,比如 web 访问服务,邮件的收发服务,文件的上传下载服务,用户在找到服务器以后如何去找具体的服务呢?答案就是端口号,端口号就是大厦中的房间号,在服务器中通过端口号区分不同的服务。
- 也就是说,服务器中的各种应用,要想向外界提供服务,必须要占用一个端口号。
- 通常 web 应用占用 80 端口,在浏览器中访问应用时 80 可以省略,因为默认就访问 80。
43、URL
- URL:统一资源定位符,表示我们要访问的资源在哪以及要访问的资源是什么。
- protocol :// hostname [:port可选] / path
- http :// www.example.com : 80/ index.html
44、前台和后台,前端和后端
- 前台和后台都是指用户界面。前台是为客户准备的,每个人都可以访问的用户界面。后台是为网站管理员准备的,只有登录以后才能访问的用户界面,用于管理网站应用中的数据。
- 前端是指开发客户端应用的程序员。
- 后端是指开发服务器端应用程序的程序员。
45、开发环境的说明
- 在开发环境中,开发者机器既充当了客户端的角色又充当了服务器的角色。
- 本机IP: 127.0.0.1
- 本机域名: localhost
46、创建 web server
- 创建软件层面的 web 服务器,用于控制资源要如何被访问。