ES6模块化与异步编程
ES6 模块化
::: tip
ES6 模块化规范是浏览器端与服务器端通用的模块化开发规范。它的出现极大的降低了前端开发者的模块化学习成本,开发者不需再额外学习 AMD、CMD 或 CommonJS 等模块化规范。
:::
ES6 模块化规范中定义:
- 每个 js 文件都是一个独立的模块
- 导入其它模块成员使用 import 关键字
- 向外共享模块成员使用 export 关键字
ES6 的模块化3种用法
默认导出的语法: export default 默认导出的成员
1 | //01_test.js |
默认导入的语法:import 接收名称 from '模块标志符'
默认导入时的接收名称可以任意名称,只要是合法的成员名称即可
1 | // 从 01_test.js 模块导入共享的成员 |
每个模块中,只允许使用唯一的一次 export default,否则会报错!
按需导出的语法: export 按需导出的成员
1 | // 02_test.js |
按需导入的语法: import { s1 } from '模块标识符'
1 | // 导入 02_test.js 模块成员 |
::: warning 注意事项
:::
- 每个模块中可以使用多次按需导出
- 按需导入的成员名称必须和按需导出的名称
保持一致
- 按需导入时,可以使用
as
关键字进行重命名 - 按需导入可以和默认导入一起使用
::: tip
如果只想单纯地执行某个模块中的代码,并不需要得到模块中向外共享的成员。此时,可以直接导入并执行模块代码
:::
1 | // 03_test.js |
1 | // 导入 |
Promise
回调地狱
多层回调函数的相互嵌套,就形成了回调地狱
1 | setTimeout(() => { |
回调地狱的缺点:
- 代码耦合性太强,牵一发而动全身,难以维护
- 大量冗余的代码相互嵌套,代码的可读性变差
::: tip
为了解决回调地狱的问题,ES6(ECMAScript 2015)中新增了 Promise 的概念。
:::
Promise 的基本概念
Promise
是一个构造函数- 我们可以创建
Promise
的实例const p = ne w Promise()
new
出来的Promise
实例对象,代表一个异步操作
- 我们可以创建
Promise.prototype
上包含一个.then()
方法- 每一次
new Promise()
构造函数得到的实例对象, - 都可以通过原型链的方式访问到
.then()
方法,例如p.then()
- 每一次
.then()
方法用来预先指定成功和失败的回调函数p.then(成功的回调函数,失败的回调函数)
p.then(result => { }, error => { })
- 调用
.then()
方法时,成功的回调函数是必选
的、失败的回调函数是可选的
基于回调函数按顺序读取文件内容
1 | import fs from "fs"; |
调用 then-fs
提供的 readFile()
方法,可以异步地读取文件的内容,它的返回值是 Promise
的实例对象。因此可以调用 .then()
方法为每个 Promise 异步操作指定成功和失败之后的回调函数
Promise 支持链式调用,从而来解决回调地狱的问题
1 | import thenFs from 'then-fs' |
Promise.all() 与 Promise.race() 方法
Promsie 方法
::: tip
Promise.all()
方法会发起并行的 Promise
异步操作,等所有的异步操作全部结束后才会执行下一步的 .then
操作(等待机制)
:::
1 | import thenFs from "then-fs"; |
::: tip
Promise.race()
方法会发起并行的 Promise
异步操作,只要任何一个异步操作完成,就立即执行下一步的.then
操作(赛跑机制)
:::
1 | import thenFs from "then-fs"; |
async/await
::: tip async/await 是 ES8(ECMAScript 2017)引入的新语法,用来简化 Promise 异步操作
:::
.then
链式调用的优点:解决了回调地狱的问题.then
链式调用的缺点:代码冗余、阅读性差、不易理解
async/await 简化 Promise 异步操作的使用
1 | import thenFs from "then-fs"; |
::: warning async/await 的使用注意事项
:::
- 如果在 function 中使用了 await,则 function 必须被 async 修饰
- 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行
EventLoop(事件循环)
JavaScript 主线程从“任务队列”中读取异步任务的回调函数,放到执行栈中依次执行。这个过程是循环不断的,所以整个的这种运行机制又称为 EventLoop(事件循环)。
结合 EventLoop 分析输出的顺序
1 | import thenFs from "then-fs"; |
::: details 查看答案
ADCB
- A 和 D 属于同步任务。会根据代码的先后顺序依次被执行
- C 和 B 属于异步任务。它们的回调函数会被加入到任务队列中,等待主线程空闲时再执行
:::
宏任务和微任务
JavaScript 把异步任务又做了进一步的划分,异步任务又分为两类,分别是:
- 宏任务(macrotask)
- 异步 Ajax 请求、
- setTimeout、setInterval、
- 文件操作
- 其它宏任务
- 微任务(microtask)
- Promise.then、.catch 和 .finally
- process.nextTick
- 其它微任务
::: tip 宏任务和微任务的执行顺序
:::
每一个宏任务执行完之后,都会检查是否存在待执行的微任务,如果有,则执行完所有微任务之后,再继续执行下一个宏任务。
::: tip 宏任务和微任务日常例子
:::
去银行办业务的场景
- 小云和小腾去银行办业务。首先,需要取号之后进行排队
- 宏任务队列
- 假设当前银行网点只有一个柜员,小云在办理存款业务时,小腾只能等待
- 单线程,宏任务按次序执行
- 小云办完存款业务后,柜员询问他是否还想办理其它业务?
- 当前宏任务执行完,检查是否有微任务
- 小云告诉柜员:想要买理财产品、再办个信用卡、最后再兑换点马年纪念币?
- 执行微任务,后续宏任务被推迟
- 小云离开柜台后,柜员开始为小腾办理业务
- 所有微任务执行完毕,开始执行下一个宏任务
::: tip 宏任务和微任务练习例子
:::
1 | setTimeout(function () { |
::: details 查看答案
2431
分析:
先执行所有的同步任务
第 6 行 ,第 12 行
注:promise 是同步 ,promise.then 回调才是异步
在执行所有的微任务
- 第9行
再执行下一个宏任务
- 第2行
:::