Vue3.0学习之铺垫知识总结
一、ES6模块化
1.1、前端模块化规范的分类
- 在ES6模块化规范诞生之前,JavaScript社区已经尝试并提出了AMD、CMD、CommonJS等模块化规范。
- 但是:这些社区的模块化标准,存在一定的差异性与局限性、并不是浏览器与服务器通用的模块化标准。
- AMD和CMD适用于
浏览器端
的JavaScript模块化。 - CommonJS适用于
服务器端
的JavaScript模块化。
- AMD和CMD适用于
- 问题:太多的模块化规范给开发者增加了学习的难度与开发的成本,因此ES6模块化规范统一诞生了!
1.2、什么是ES6模块化规范
- ES6模块化规范是浏览器与服务器端通用的模块化开发规范。它的出现极大的降低了前端开发者的模块化学习成本,开发者不需要再额外学习AMD、CMD或CommonJS等模块化规范。
1.3、ES6模块化规范定义
- 每个js文件都是一个独立的模块
- 导入其它模块成员使用import关键字
- 向外共享模块成员使用export关键字
1.4、体验ES6模块化必备条件
- node.js环境
- 确保安装v14.15.1+版本的node.js
- 验证本地node版本:dos窗口执行命令:node -v
- 可从官网下载最新安装包,直接重新安装即可覆盖原来的版本。
- 在package.json的根节点中添加"type":"module"节点
- 确保安装v14.15.1+版本的node.js
二、ES6模块化的使用
2.1、默认导出
- 默认导出语法:export default 默认导出的成员
- 新建js文件内容如下:
// 定义模块私有成员 n1
let n1 = 10
// 定义模块私有成员 n2
let n2 = 20
// 定义模块私有方法 show
function show(){}
// 使用export default 默认导出语法,向外共享 n1 和 show 两个私有成员
export default{
n1,
show
}
- 【注意】每个js文件中只能有一个export导出语法,所有导出变量都写入这一个语法中。
2.2、默认导入
- 默认导入语法:import 自定义接受名称 from ‘模块标识符(需要导入的js文件名称路径)’
// 导入01默认导出的js文件, 并且定义接受变量为:m1
import m1 from './01默认导出.js'
//打印输出m1的内容:
console.log(m1)
- 可使用node命令执行js文件进行测试,效果如下:
- 【注意】定义导入变量需要合法,不能以数字开头。
2.3、按需导出
- 【语法】export 按需导出的成员
// 向外按需导出变量 s1
export let s1 = 'douglas'
// 向外按需导出变量 s2
export let s2 = 'love'
// 向外按需导出方法 say
export function say(){}
2.4、按需导入
- 【语法】import {s1} from ‘模块标识符(js文件相对路径)’
// 按需导入 s1 s2 say 成员变量
import {s1,s2,say} from './03按需导出.js'
// 打印输出 s1 s2 say 成员内容
console.log(s1)
console.log(s2)
console.log(say)
-
【执行】输出如下:
-
【注意事项】
- 每个模块中可以使用多次按需导出
- 按需
导入的成员名称
必须和按需导出的名称
保持一致 - 按需导入时,可以使用
as关键字
进行重命名 - 按需导入可以和默认导入一起使用
2.5、直接导入
- 【概念】如果
只想单纯地执行某个模块中的代码
,并不需要得到模块中向外共享的成员。此时,可以直接导入并执行模块代码,示例代码如下:
// 在当前模块中执行一个 for 循环操作
for(let i = 0; i<3; i++){
console.log(i)
}
-----------------------分割线------------------
// 直接导入并执行模块代码,不需要得到模块向外共享的成员(在新的js文件中直接导入,执行新的js文件就会被加载执行)
import './05直接导入.js'
三、Promise
3.1、回调地狱
多层回调函数的相互嵌套
,就形成了回调地狱
。示例如下:
setTimeout(()={
// 第一层回调函数
console.log('延时器 1 秒后输出')
setTimeout(()={
//第二层回调函数
console.log('延时器 2 秒后输出')
setTimeout(()={
//第三层回调函数
console.log('延时器 3 秒后输出')
},3000)
},2000)
},1000)
- 【缺点】
- 代码耦合性太强,牵一发而动全身,难以维护。
- 大量冗余的代码相互嵌套,代码的可读性变差。
3.2、基本概念
- 【
Promise是一个构造函数
】- 我们可以创建Promise的实例对象:const p = new Promise()
- new 出来的Promise 实例对象,代表一个异步操作
- 【
Promise.prototype 上包含一个.then()方法
】- 每一次new Promise()构造函数得到的实例对象,都可以
通过原型链的方式
访问到.then()方法,例如:p.then()
- 每一次new Promise()构造函数得到的实例对象,都可以
- 【
.then()方法用来预先指定成功和失败的回调函数
】- p.then(
成功的回调函数
,失败的回调函数
) - p.then(result =>{},error =>{})
- 调用.then()方法时,成功的回调函数时必选的,失败的回调函数时可选的
- p.then(
3.3、基于then-fs异步的读取文件内容
- 由于node.js官方提供的fs模块
仅支持
以回调函数的方式
读取文件,不支持Promise的调用方式
。因此,需要按运行命令安装then-fs
这个第三方包,从而支持我们基于Promise的方式读取文件的内容:
npm install then-fs
- 【基本使用】调用then-fs提供的
readFile()
方法,可以异步地读取文件的内容,它的返回值是Promise的实例对象
。因此可以调用.then()方法
为每个Promise异步操作指定成功
和失败
之后的回调函数。示例如下:
/**
* 基于Promise的方式读取文件
*/
import thenFs from 'then-fs'
// 注意: .then()中的失败回调是可选的,可以被省略
thenFs.readFile('./files/1.txt','utf8').then(r1=>{console.log(r1)},err1=>{console.log(err1.message)})
thenFs.readFile('./files/2.txt','utf8').then(r2=>{console.log(r2)},err2=>{console.log(err2.message)})
thenFs.readFile('./files/3.txt','utf8').then(r3=>{console.log(r3)},err3=>{console.log(err3.message)})
- 以上代码使用node命令运行即可,例如:node 05.js
- 【注意问题】以上方式执行,读取文件执行顺序是随机的,需要按顺序执行看如下:
3.4、.then() 方法的特性
- 如果上一个.then()方法中返回一个新的Promise实例对象,则可以通过下一个.then()继续进行处理。通过.then()方法的
链式调用
,就解决了回调地狱的问题。 - 链式调用代码如下:
// ------------------------链式调用---有序调用--------
thenFs.readFile('./files/1.txt','utf8') // 1.返回值是Promise的实例对象
.then((r1) => { // 2.通过.then 为第一个 Promise 实例指定成功之后的回调函数
console.log(r1)
return thenFs.readFile('./files/3.txt','utf8') // 3.在第一个 .then 中返回一个新的 Promise 实例对象
})
.then((r3) => { // 4. 继续调用 .then 为上一个 .then 的返回值(新的 Promise 实例) 指定成功之后的回调函数
console.log(r3)
return thenFs.readFile('./files/2.txt','utf8') // 5. 在第二个 .then 中再返回一个新的 Promise实例对象
})
.then((r2) => { // 6.继续调用 .then 的返回值(新的 Promise 实例) 指定成功之后的回调函数
console.log(r2)
})
3.5、通过.catch捕获错误
- 在Promise的链式操作中如果发生了错误,可以使用Promise.prototype.catch方法进行捕获和处理;
- 捕获处理编码如下:
// --------------------------链式调用错误--捕获---------------------
thenFs.readFile('./files/11.txt','utf8') //1.文件不存在导致读取失败 后面的.then都不会执行
.then((r1) => {
console.log(r1)
})
.catch(err => { //2.捕获第一行的错误,并输出错误的消息
console.log(err.message)
})
// 注意:catch放在所有.then之后会捕获所有异常,但任何一个文件读取失败都会导致之后的所有.then无法执行; 若catch只放在某个.then之后,则只会捕获当前.then的异常,不影响其它文件读取.then的执行;
3.6、Promise.all()方法
- Promise.all()方法会发起并行的Promise异步操作,等所有的异步操作全部结束后,才会执行下一步的.then操作(等待机制),
- 示例代码如下:
//1.定义一个数组,存放3个读取文件的异步操作
const promiseArr = [
thenFs.readFile('./files/1.txt','utf8'),
thenFs.readFile('./files/2.txt','utf8'),
thenFs.readFile('./files/3.txt','utf8')
]
//2.将Promise 的数组,作为Promise.all() 的参数
Promise.all(promiseArr)
.then(([r1,r2,r3]) => { //2.1所有文件读取成功(等待机制)
console.log(r1,r2,r3)
})
.catch(err => { //2.2 捕获Promise异步操作中的错误
console.log(err.message)
})
3.7、Promise.race()方法
- Promise.race()方法会发起并行的Promise异步操作,只要任何一个一步操作完成,就立即执行下一步的.then操作(赛跑机制)。
- 示例代码如下:
//1.定义一个数组,存放3个读取文件的异步操作
const promiseArr = [
thenFs.readFile('./files/1.txt','utf8'),
thenFs.readFile('./files/2.txt','utf8'),
thenFs.readFile('./files/3.txt','utf8')
]
//2.将Promise 的数组,作为Promise.race() 的参数
Promise.race(promiseArr)
.then((result) => { //2.1 只要任何一个异步操作完成,就立即执行成功的回调函数(赛跑机制)
console.log(result)
})
.catch(err => { //2.2 捕获Promise异步操作中的错误
console.log(err.message)
})
// 注意:只会输出读取速度最快的那个文件结果!!!
3.8、基于Promise封装异步读取文件的方法
-
方法封装的要求:
- 1.方法的名称要定义为getFile
- 2.方法接受一个形参fpath
- 3.方法的返回值为Promise实例对象
-
创建具体的异步操作
- 如果想要创建具体的异步操作,则需要在new Promise()构造函数期间,传递一个function函数,将具体的异步操作定义到function函数内部。
- 实例代码:
// 1. 方法名称为getFile // 2. 方法接受一个形参 fpath,表示要读取的文件的路径 function getFile(fpath){ //3. 方法的返回值为 Promise 的实例对象 return new Promise(function(){ //4. 下面执行的是针对与文件的具体操作,读文件的异步操作 thenFs.readFile(fpath,'utf8',(err,dataStr) => { console.log('-----'+dataStr) }) }) } getFile('./files/1.txt')
-
调用resolve和reject回调函数
- Promise异步操作的结果,可以调用resolve或reject回调函数进行处理。
- 实例代码:
function getFile(fpath){ //1. resolve是成功的回调函数 reject是失败的回调函数 return new Promise(function(resolve, reject){ //2. 下面执行的是针对与文件的具体操作,读文件的异步操作 thenFs.readFile(fpath,'utf8',(err,dataStr) => { //3.如果读取失败,则调用失败的回调函数 if(err) return reject(err) //4.如果读取成功,则调用成功的回调函数,返回内容 resolve(dataStr) }) }) } // getFile("/files/1.txt").then(成功回调函数,失败回调函数) getFile("./files/1.txt").then((r1) =>{console.log(r1)}, (err) => {console.log(err.message)}) getFile("./files/1.txt") .then((r1) => { console.log(r1) }) .catch((err) => { console.log(err.message) })
3.9、async和await基本使用
async/await
是ES8引入的新语法,用来简化Promise异步操作。在async/await出现之前,开发者只能通过链式.then()的方式处理Promise异步操作。- 使用async与await异步处理示例:
import thenFs from 'then-fs';
//1.按照文件顺序读取文件
async function getAllFile(){
const r1 = await thenFs.readFile('./files/1.txt','utf8')
console.log(r1)
const r2 = await thenFs.readFile('./files/2.txt','utf8')
console.log(r2)
const r3 = await thenFs.readFile('./files/3.txt','utf8')
console.log(r3)
}
getAllFile();
-
链式调用 .then的优点:解决了回调地狱的问题
-
链式调用.then的缺点:代码冗余、阅读性差、不易理解
-
【注意事项】
- 1.如果在function中使用了await,则function必须被async修饰
- 2.在async方法中,第一个await之前的代码会同步执行,await之后的代码会异步执行
console.log('A') async function getAllFile(){ console.log('B') const r1 = await thenFs.readFile('./files/1.txt','utf8') const r2 = await thenFs.readFile('./files/2.txt','utf8') const r3 = await thenFs.readFile('./files/3.txt','utf8') console.log(r1,r2,r3) console.log('D') } getAllFile(); console.log('C')
- 执行结果:
四、EventLoop
4.1、同步和异步任务
-
JavaScript是一门
单线程执行
的编程语言。也就是说,同一时间只能做一件事情。多任务执行需要排队等待; -
单线程执行任务队列存在的问题:
- 如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死的问题。
-
【解决假死问题】为了防止某个耗时任务导致程序假死的问题,JavaScript把待执行的任务分为两类:
- 【同步任务】synchronous
- 1.又叫做非耗时任务,指的是在主线程上排队执行的任务
- 2.只有前一个任务执行完毕,才能执行后一个任务
- 【异步任务】asynchronous
- 1.又叫做耗时任务,异步任务由JavaScript委托给宿主环境进行执行
- 2.当异步任务执行完毕后,会通知JavaScript主线程执行异步任务的回调函数
- 【同步任务】synchronous
4.2、同步与异步的执行过程
4.3、宏任务和微任务
-
JavaScript把异步任务细化分为两类:
- 【宏任务】macrotask
- 1.异步Ajax请求
- 2.setTimeout、setInterval
- 3.文件操作
- 4.其它宏任务
- 【微任务】microtask
- 1.Promise.then、.catch和.finally
- 2.process.nextTick
- 3.其它微任务
- 【宏任务】macrotask
-
如下图结构:
-
【宏任务和微任务执行顺序】如下图:
-
注意:每一个宏任务执行完毕后,都会检查是否存在待执行的微任务,如果存在,则优先执行完所有微任务之后,再继续执行下一个宏任务。
五、API接口案例
5.1、案例需求
- 基于MySQL数据库+Express对外提供用户列表的API接口服务,用的技术如下:
- 1.第三方包express和mysql2
- 2.ES6模块化
- 3.Promise
- 4.async/await
- 主要实现步骤
- 1.搭建项目的基本结构
- 2.创建基本的服务器
- 3.创建db数据库操作模块
- 4.创建user_ctrl业务模块
- 5.创建user_router路由模块
- 搭建项目的基本结构
- 1.启用ES6模块化支持
- 在package.json中声明"type":“module”
- 2.安装第三方依赖包
- 运行npm install express@4.17.1 mysql2@2.2.5
- 1.启用ES6模块化支持
5.2、实际操作
-
1.新建一个文件夹,使用VScode打开
-
2.使用VScode终端执行初始化命令:npm init -y
-
3.打开package.json,在头部新增模块化类型: “type”: “module”
-
4.在终端执行命令安装三方依赖包:npm i express@4.17.1 mysql2@2.2.5
-
5.创建基本的服务器
- 1.新建app.js文件
- 2.编写基本代码如下:
//1.导入服务器模块 import express from 'express' //2.创建服务器实例 const app = express() //3.设置服务器监听端口 app.listen(80, () => { console.log('server running at http://127.0.0.1') })
- 3.执行启动服务器:在终端执行命令:node app.js
-
6.创建db数据库操作模块
- 1.新建一个文件夹db
- 2.在db中新建一个文件index.js
- 3.实现编码连接数据库如下:
//1.导入数据库组件 import mysql from 'mysql2' //2.创建数据库连接池 const pool = mysql.createPool({ host: '101.43.89.105', port: '3306', // 数据库端口 database: '' //数据库名称 user: '', //用户名 password: '' // 密码 }) //3.将数据库连接暴露到全局,但需要暴露数据库连接的Promise实例对象 export default pool.promise()
-
7.创建user_ctrl模块
- 1.新建一个文件夹controller
- 2.新建一个文件user_ctrl.js
- 3.编写查询数据库的语句,代码如下:
//1.导入数据库db操作模块连接 import db from "../db/index.js"; //2.使用ES6按需导出语法,将getAllUser查询方法导出出去 export async function getAllUser(req, res){ const [rows] = await db.query('select id, name, pwd, state from user') console.log(rows) res.send({ status: 0, message: '查询成功', data: rows }) }
- 结果如下:
-
8.创建user_router模块
- 1.创建一个文件夹router
- 2.创建一个文件user_router.js
- 3.进行路由与db查询请求适配,编码如下:
//1.导入express服务器模块实例 import express from 'express' //2.导入用户模块的查询请求方法 -- 按需导入 import { getAllUser } from '../controller/user_ctrl.js' //3.创建路由实例 const router = new express.Router() //4.适配请求路由与用户的查询db请求 router.get('/user', getAllUser) //5.将路由暴露到全局 export default router
- 4.在app.js中挂载请求到路由模块中,编码如下:
import express from 'express' //导入路由模块 import userRouter from './router/user_router.js' const app = express() //挂载请求到路由模块中,增加请求路由前缀/api app.use('/api', userRouter) app.listen(80, () => { console.log('server running at http://127.0.0.1') })
- 5.启动服务,测试
- 访问地址:127.0.0.1:80/api/user
- 6.结果如下:
-
9.使用try…catch捕获异常
- 如果查询db失败,防止程序崩溃,则使用try…catch进行异常捕获处理,编码如下:
//1.导入数据库db操作模块连接 import db from "../db/index.js"; //2.使用ES6按需导出语法,将getAllUser查询方法导出出去 export async function getAllUser(req, res){ try { const [rows] = await db.query('select id, name, pwd, state from user') console.log(rows) res.send({ status: 0, message: '查询成功', data: rows }) } catch (e) { res.send({ status: 1, message: '查询失败', desc: e.message }) } }