title: ES6模块化
categories: Javascript
tag: JS
date: 2021-09-27 19:45:34
ES6 模块化
ES6 模块化规范是浏览器端和服务器端通用的模块化开发规范,它的出现大大降低了前端开发者的模块化学习成本,不需要再学习 AMD、CMD、或 CommonJS 等模块化规范。
ES6 款模块化规范中定义:
- 每个 JS 都是独立的模块
- 导入其他模块使用 import 关键字
- 向外共享其他模块使用 export 关键字
在 nodeJS 使用 ES6 模块化
- 确保安装了 v14.15.1 或高于的 node 版本
- 输入
npm init -y
。在 package.json 里面添加"type": "module"
ES6 模块化语法
- 默认导出与导入
- 按需导出与导入
- 直接导入并执行模块中的代码
默认导出与默认导入
export default 默认导出的成员对象
let n1 = 10
let n2 = 20
function show() {}
export default {
n1,
show
}
import 接受名称 from '模块的路径'
import m1 from './1.js'
console.log(m1) //{ n1: 10, show: [Function: show] }
注意事项
- 在每一个模块中,只允许使用一次默认导出。
- 默认导入时的接受名称可以任意名称,只要是合法的名称即可
按需导出与按需导入
export 按需导出的成员
export let s1 = 'aaa'
export let s2 = 'ccc'
export function say() {}
import {s1} from '路径'
import { s1, s2, say } from './3.js'
console.log(s1, s2, say)
注意事项
- 每个模块中可以使用多次按需导出
- 按需导入的名称必须和按需导出的名称保持一致
- 按需导入时,可以使用 as 关键字进行重命名
import { s1, s2 as str2, say } from './3.js'
console.log(s1, str2, say)
- 按需导入和默认导入可以一起使用
直接导入并执行模块中的代码
如果只是想单纯的执行某个模块中的代码,并不需要得到模块中向外共享成员,,此时,可以直接导入并执行模块代码
在这个4.js文件夹中,我们没有export哦。
for (let i = 0; i < 3; i++) {
console.log(i)
}
import './4.js '
此时运行 3.js 的话,控制台直接打印 0,1,2
Promise
回调地狱
多层回调函数的相互嵌套,就形成了回调地狱。实例代码如下
缺点:
- 代码耦合性太强,牵一发而动全身,难以维护
- 大量冗余的代码相互嵌套,代码的可读性变差
如何解决回调地狱?
ES6 中引入了 Promise 的概念
Promise 的基本概念
- Promise 是一个构造函数
- 我们可以创建 Promise 的实例
const p=new Promise()
- new 出来的 Promise 实例对象,代表一个异步操作
Promise.prototype
上有一个.then()
方法.then()
方法用来预先制定成功和失败的回调函数
- p.then(成功的回调函数,失败的回调函数)
- p.then(result=>{},error=>{})
- 调用.then()方法时,成功的回调函数是必选的,失败的回调函数是可选的
基于 Promise 按顺序读取文件内容
由于 node.js 官方提供的 fs 模块是以回调函数的方式读取文件,不支持 Promise 的调用方式,因此,要执行如下命令。安装 then-fs 这个第三方包,从而支持我们基于 Promise 的方式读取文件的内容npm install then-fs
- then-fs 的用法
调用 then-fs 提供的 readFile()方法,可以异步的读取文件的内容,它的返回值是 Promise 的实例对象,因此可以调用.then()方法为每个 Promise 异步操作制定成功和失败之后的回调函数。
import thenFs from 'then-fs'
thenFs.readFile('./files/1.txt', 'utf8').then((r1) => console.log(r1))
thenFs.readFile('./files/2.txt', 'utf8').then((r2) => console.log(r2))
thenFs.readFile('./files/3.txt', 'utf8').then((r3) => console.log(r3))
运行结果:
222
333
111
可以看到无法保证文件的读取顺序
import thenFs from 'then-fs'
thenFs
.readFile('./files/1.txt', 'utf-8')
.then((r1) => {
console.log(r1)
return thenFs.readFile('./files/2.txt', 'utf8')
})
.then((r2) => {
console.log(r2)
return thenFs.readFile('./files/3.txt', 'utf8')
})
.then((r3) => {
console.log(r3)
})
运行结果:
111
222
333
可以通过.catch()捕获错误。
Promise.all()
Promise.all()方法会发起并行的 Promise 异步操作,等所有的异步操作全部结束之后才会执行下一步的.then()
操作。
const promiseArr = [
thenFs.readFile('./files/1.txt', 'utf8'),
thenFs.readFile('./files/2.txt', 'utf8'),
thenFs.readFile('./files/3.txt', 'utf8')
]
Promise.all(promiseArr).then((result) => {
console.log(result) //[ '111', '222', '333' ]
})
Promise.race()
Promise.race()会发起并行的 Promise 异步操作,只要任何一个异步操作完成,就立即使用下一步的.then()
操作(赛跑机制)。
const promiseArr = [
thenFs.readFile('./files/1.txt', 'utf8'),
thenFs.readFile('./files/2.txt', 'utf8'),
thenFs.readFile('./files/3.txt', 'utf8')
]
Promise.race(promiseArr).then((result) => {
console.log(result) //谁快就把它当作result
})
基于 Promise 封装读文件的方法
- 方法名称是
getFile
- 方法要接受一个形参
fpath
,表示要读取的文件的路径 - 方法的返回值为
Promise
对象
import fs from 'fs'
function getFile(fpath) {
return new Promise(function (resolve, reject) {
fs.readFile(fpath, 'utf8', (err, data) => {
if (err) return reject(err)
resolve(data)
})
})
}
getFile('./files/1.txt')
.then((success) => console.log(success))
.catch((err) => console.log(err))
async/await
async/await
是 ES8 引入的新语法,用来简化Promise
操作,在async/await
出现之前,开发者只能使用.then()
.then()链式调用的优点:解决了回调地狱的问题
.then()链式调用缺点:代码冗余,阅读性差,不易理解
//。async/await用法
async function getAllFiles() {
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)
}
getAllFiles()
注意事项
- async/await 是成对出现的
- 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行。
console.log('A')
async function getAllFiles() {
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')
}
getAllFiles()
console.log('C')
A
B
C
111 222 333
D
EventLoop
JS 是一门单线程执行的编程语言,也就是说,用一时间只能做一件事情。
单线程执行任务队列的问题:
如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死的问题。
同步任务和异步任务
为了防治某个耗时任务导致程序假死的问题,JS 把待执行的任务分为了两类:
- 同步任务(synchronous)
- 又叫做非耗时任务,指的是在主线程上排队执行的那些任务
- 只有前一个任务执行完毕,才能执行下一个任务
- 异步任务(asynchronous)
- 又叫做耗时任务,异步任务由
JS
委托给宿主环境进行执行 - 当异步任务执行完成之后,会通知
JS
主线程执行异步任务的回调函数
- 又叫做耗时任务,异步任务由
同步任务和异步任务的执行过程
- 同步任务是由 JS 主线程按次序完成的
- 异步任务委托给宿主环境执行
- 已完成的异步任务对应的回调函数会被加入到任务队列中等待执行
- JS 主线程的执行栈被清空之后,会读取任务队列中的回调函数,次序执行
- JS 主线程不断重复上面的第 4 步
JavaScript 主线程从”任务队列”中读取异步任务的回调函数,放到执行栈中依次执行,这个过程是循环不断的,所以整个的这种运行机制又称为 EventLoop(事件循环)
console.log('A')
thenFs.readFile('./files/1.txt', 'utf8').then((dataStr) => {
console.log('B')
})
setTimeout(() => {
console.log('C')
}, 0)
console.log('D')
运行结果
A
D
C
BA 和 D 属于同步任务,在主线程里面,会根据代码执行的先后顺序执行
B 和 C 属于异步任务,C 只是 0ms。相当于我把 C 委托给宿主环境,宿主环境不需要时间,就把回调函数放在了任务队列。然后 B 再执行
宏任务和微任务
JS 把异步任务又做了进一步的划分,异步任务分为两类。分别是
- 宏任务
- 异步 Ajax 请求
- setTimeout,setInterval
- 文件操作
- 其他宏任务
- 微任务
- Promise.then、Promise.catch 和 Promise.finally
- Process.nextTick
- 其他微任务
执行顺序:每一次宏任务执行完之后,都会检查是否存在待执行的微任务。
如果有,就会执行完所有微任务之后,再继续执行下一个宏任务
宏任务和微任务理解
比如,去银行办业务。
- 小明和小红去银行办业务,需要取号排队
- 宏任务队列
- 假设银行只有一个柜员,小明在办理业务的时候。小红只能等待
- 单线程,宏任务按次序执行
- 小明办理完存款业务之后,柜员询问是否还想办理其他业务
- 当前宏任务执行完,检查是否有微任务
- 小明会告诉柜员,还想办理理财产品和办张信用卡
- 执行微任务,后续宏任务被推迟
- 小明离开柜台之后,柜员开始为小红办理
- 所有微任务执行完毕,开始执行下一个叫宏任务
经典面试题
面试 1
setTimeout(function () {
console.log('1')
})
new Promise(function (resolve) {
console.log('2')
resolve()
}).then(function () {
console.log('3')
})
console.log('4')
运行结果
2
4
3
1首先定时器是宏任务,只要 new 了一个 Promise 对象,就会执行这个函数。打印 2
然后。
.then
是微任务,所以执行同步任务打印 4此时没有同步任务,就去微任务中,打印 3
最后执行宏任务打印 4
面试 2
console.log('1')
setTimeout(function () {
console.log('2')
new Promise(function (resolve) {
console.log('3')
resolve()
}).then(function () {
console.log('4')
})
})
new Promise(function (resolve) {
console.log('5')
resolve()
}).then(function () {
console.log('6')
})
setTimeout(function () {
console.log('7')
new Promise(function (resolve) {
console.log('8')
resolve()
}).then(function () {
console.log('9')
})
})
运行结果
1
5
6
2
3
4
7
8
9