ES6模块化
ES6模块化规范是浏览器端与服务器端通用的模块化开发规范。它的出现极大的降低了前端开发者的模块化学习成本,开发者不再需要需要额外学习AMD、CMD或CommonJS等模块化规范
ES6模块化规范中定义:
- 每个js文件都是一个独立的模块
- 导入其他模块成员使用import关键字
- 向外共享模块成员使用export关键字
在node.js中体验ES6模块化
- 安装v14.15.1或更高版本的node.js
- 在package.json的根节点中添加type节点
ES6模块化的三种基本语法:
- 默认导出:export default 默认导出的成员
let n1 = 10 //定义模块私有成员 n1
let n2 = 20 //定义模块私有成员n2(外界访问不到n2,因为它没有被共享出去)
function show() {} //定义模块私有方法 show
export default { //使用export default 默认导出语法,向外共享n1和show两个成员
n1,
show
}
默认导入:import 接收名称 from ‘模块标识符’
// 从01.js模块中导入export default 向外共享的成员
// 使用m1成员进行接收
import m1 from './01默认导出.js'
console.log(m1);
// 打印输出结果为:
// { n1: 10, show: [Function: show] }
注意事项:1.1每个模块中只允许使用唯一一次export default,否则会报错
1.2默认导入时的接收名称可以任意名称,只要合法即可
- 按需导出:export 按需导出的成员
// 当前模块为03.js
// 向外按需导出变量s1
export let s1 = 'aaa'
// 向外按需导出变量s2
export let s2 = 'bbb'
// 向外按需导出方法 say
export function say() {}
按需导入:import{s1} from ‘模块标识符’
// 导入模块成员
import { s1,s2,say } from './03按需导出.js';
console.log(s1);
console.log(s2);
console.log(say);
注意事项:每个模块中可以使用多次按需导出
按需导入的成员名称必须和按需导出的名称保持一致
按需导入时,可以使用as关键字进行重命名
按需导入可以和默认导入一起使用
- 直接导入并执行模块中的代码
Import ’模块标识符 ’
// 当前文件模块为05.js
for(let i=0;i<3;i++){
console.log(i);
}
// ---------------分割线------------
// 当前文件模块为06.js
// 直接导入并执行模块代码,不需要得到模块向外共享的成员
import './05.直接运行模块中的代码.js'
Promise
回调地狱:多层回调函数的相互嵌套
setTimeout(() => { //第1层回调函数
console.log('延迟1秒后输出');
setTimeout(() => {//第2层回调函数
console.log('延迟2秒后输出');
setTimeout(() => {//第3层回调函数
console.log('延迟3秒后输出');
}, 3000)
}, 2000)
}, 1000)
回调地狱的缺点:
- 代码耦合性太强,牵一发而动全身,难以维护
- 大量冗余的代码相互嵌套,代码的可读性变差
为了解决回调地狱的问题,ES6中新增了promise的概念
promise的基本概念
① Promise 是一个构造函数
⚫ 我们可以创建 Promise 的实例 const p = new Promise()
⚫ new 出来的 Promise 实例对象,代表一个异步操作
② Promise.prototype 上包含一个 .then() 方法
⚫ 每一次 new Promise() 构造函数得到的实例对象,
⚫ 都可以通过原型链的方式访问到 .then() 方法,例如 p.then()
③ .then() 方法用来预先指定成功和失败的回调函数
⚫ p.then(成功的回调函数,失败的回调函数)
⚫ p.then(result => { }, error => { })
⚫ 调用 .then() 方法时,成功的回调函数是必选的、失败的回调函数是可选的
1.基于回调函数按顺序读取文件内容
import fs from 'fs'
//读取文件1.txt
fs.readFile('./files/1.txt', 'utf8', (err1, r1) => {
if (err1) return console.log(err1.message);
console.log(r1);
//读取文件2.txt
fs.readFile('./files/2.txt', 'utf8', (err2, r2) => {
if (err2) return console.log(err2.message);
console.log(r2);
//读取文件3.txt
fs.readFile('./files/3.txt', 'utf8', (err3, r3) => {
if (err3) return console.log(err3.message);
console.log(r3);
})
})
})
2.基于then-fs读取文件内容
由于node,js官方提供的fs模块仅支持以回调函数的方式读取文件,不支持promise的调用方式,所以要先运行如下的命令,安装then-fs这个包
npm install then-fs
3.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);})
注意:上述的代码无法保证文件的读取顺序,需要做进一步的改进!
4.then()方法的特性
如果上一个 .then() 方法中返回了一个新的Promise 实例对象,则可以通过下一个 .then() 继续进行处理。通过 .then() 方法的链式调用,就解决了回调地狱的问题。
5.基于promise按顺序读取文件的内容
promise支持链式调用,从而来解决回调地狱的问题
import thenFs from 'then-fs'
thenFs.readFile('./files/1.txt', 'utf8') //返回值是promise的实例对象
.then(result1 => { //通过.then为第一个promise实例指定成功之后的回调函数
console.log(result1)
return thenFs.readFile('./files/2.txt', 'utf8') //在第一个.then中返回一个新的prmise实例对象
})
.then(result2 => { //继续调用.then,为上一个.then的返回值指定成功之后的回调函数
console.log(result2)
return thenFs.readFile('./files/3.txt', 'utf8')
})
.then(result3 => { console.log(result3) })
6.通过.catch捕获错误
在 Promise 的链式操作中如果发生了错误,可以使用 Promise.prototype.catch 方法进行捕获和处理:
import thenFs from 'then-fs'
thenFs.readFile('./files/11.txt', 'utf8')
.then(result1 => {
console.log(result1)
return thenFs.readFile('./files/2.txt', 'utf8')
})
.then(result2 => {
console.log(result2)
return thenFs.readFile('./files/3.txt', 'utf8')
})
.then(result3 => { console.log(result3) })
.catch(err=>{ //捕获第一行发生的错误,并输出错误的信息
console.log(err.message);
})
async/await
async/await是ES8引入的新语法,用来简化promise异步操作
1.async/await的基本使用
使用async/await简化promise异步操作的示例代码:
import thenFs from 'then-fs'
// 按照顺序读取文件1,2,3的内容
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()
注意:
① 如果在 function 中使用了 await,则 function 必须被 async 修饰
② 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行
EventLoop
1.javaScript 是单线程的编程语言。也就是说,同一时间只能做一件事情
单线程执行任务队列的问题:
如果前一个任务非常耗时时,则后续的任务就不得不一直等待,从而导致程序假死的问题
2.同步任务和异步任务
为了防止某个耗时任务导致程序假死的问题,javascript把待执行的任务分成了两类:
① 同步任务
- 又叫做非耗时任务,指的是在主线程上排队执行的那些任务
- 只有前一个任务执行完毕,才能执行后一个任务
② 异步任务
- 又叫做耗时任务,异步任务由javascript委托给宿主环境进行执行
- 当异步任务执行完成后,会通知javascript主线程执行异步任务的回调函数
3.同步任务和异步任务的执行过程
① 同步任务由 JavaScript 主线程次序执行
② 异步任务委托给宿主环境执行
③ 已完成的异步任务对应的回调函数,会被加入到任务队列中等待执行
④ JavaScript 主线程的执行栈被清空后,会读取任务队列中的回调函数,次序执行
⑤ JavaScript 主线程不断重复上面的第 4 步
在这里举个好理解的例子
大厨和徒弟,比如大厨在做饭的时候,需要一瓣蒜,他就会让徒弟剥,然后大厨继续做他的事情,徒弟剥好蒜之后,放在一个固定的位置,大厨在做完手头的事情后,看看有没有剥好的蒜,有就拿去用,没有就继续做自己的事
4.EventLoop的基本概念
javaScript主线程从“任务队列”中读取异步任务的回调函数,放到执行栈中依次执行。这个过程是循环不断的,所以整个的这种运行机制又称为EventLoop(事件循环)
宏任务和微任务
1.javascript把异步任务又做了进一步的划分,异步任务又分为两类:
① 宏任务:
- 异步Ajax请求
- setTimeout、setinterval
- 文件操作
- 其他宏任务
② 微任务:
- promise.then、 .catch和.finally
- process.nextTick
- 其他微任务
2.宏任务和微任务的执行顺序
每一个宏任务执行完之后,都会检查是否存在待执行的微任务,如果有,则执行完所有微任务之后,再继续执行下一个宏任务
分析以下代码输出的顺序
setTimeout(function(){
console.log('1');
})
new Promise(function(resolve){
console.log('2');
resolve()
}).then(function(){
console.log('3');
})
console.log('4');
正确的输出顺序是:2431
分析:1.先执行所有的同步任务
2.再执行微任务
3.再执行下一个宏任务