知识点合集:ES6模块、Promise、EVentLoop、宏任务与微任务

模块化相关

1.ES6模块化规范的分类

在ES6模块化规范诞生之前,javaScript社区已经尝试并提出了AMD、CMD、CommonJS等模块化规范。

但是,这些由社区提出的模块化标准,还是存在一定的差异性与局限性、并不是浏览器与服务器通过的模块化标准,例如:

  • AMD 和 CMD适合于浏览器端的Javascript模块化
  • CommonJS 适用于服务器端的javascript模块化

太多的模块化规范给开发者增加了学习的难度与开发的成本。因此,大一统的ES6模块化规范诞生了!

2.什么是ES6模块化规范

ES6模块化规范是浏览器端与服务器端通用的模块化开发规范。它的出现极大的降低了前端开发者的模块化学习成本,开发者不需要再额外学习AMD、CMD或CommonJS等模块化规范。
ES6模块化规范中定义:

  • 每个js文件都是一个独立的模块
  • 导入其他模块成员使用import关键字
  • 向外共享模块成员使用export关键字

3.在node.js中体验ES6模块化

node.js 中默认仅支持CommonJS模块化规范,若想基于node.js体验与学习ES6的模块化语法,可以按照如下两个步骤配置:
1)确保安装了v14.15.1或者更高版本的node.js
终端输入: node -V 或者 node --version
2)在pakage.json 的根节点中添加

"type":"module"   //节点

4.ES6的模块化的基本语法

ES6的模块化主要包含如下3种用法:
1)默认导出与默认导入
2)按需导出与按需导入
3)直接导入并执行模块中的代码

4.1 默认导出

默认导出的语法: export default 默认导出的成员

let n1 = 10 
let b2 = 10

function show(){

}
export default {  //使用 export default 默认导出语法,向外共享n1 和 show两个成员
  n1,
  show
}

----------------------分割线
//导入
import m1 from './xxxx.js'  //导入后m1中就拿到了导出的值

4.2 默认导出的注意事项

每个模块中,只允许使用唯一的一次export default,否则会报错!
默认导入时的接收名称可以任意名称,只要是合法的成员名称即可:

4.3 按需导出

//假设当前模块是test.js

//向外按需导出变量s1
export let s1 = 'aaa'
//向外按需导出方法
export function say(){}

4.4 按需导入

语法: import {s1} from ‘模块标识符’
当有多个成员时: import {s1,s2,say} from ‘./test.js’

4.5 按需导入和按需导出的注意事项

1)每个模块中可以使用多次按需导出
2) 按需导入的成员名称必须和按需导出的名称保持一致
3)按需导入时,可以使用as关键字进行重命名 : import {s1 as str1,s2,say} from ‘./test.js’
4)按需导入可以和默认导入一起用

export let s1 = 'aaa'
export let s2 = 'bbb'
export default {
  a:20,
}
--------------分割线
import info ,{s1,s2 as str2,say} form '模块.js'  //注意花括号内是按需导出的变量,花括号外的info则指向默认导出的变量,此时info为空

4.6 直接导入并执行模块中的代码

如果只想单纯的执行某个模块中的代码,并不需要得到模块中向外共享的成员。此时,可以直接导入并执行模块代码,示例代码如下:

//当前文件模块化为05_m3.js
//在当前模块执行一个for 循环操作
for(let i = 0;i < 3;i++){
  console.log(i)
}
-------------------分割线
//直接导入并执行模块代码,不需要得到模块向外共享的成员。此时,可以直接导入并执行模块代码,示例代码如下:
//当前文件模块为05_m3.js
//在当前模块中执行一个for循环操作
for(let i=0;i<3;i++){
  console.log(i)
}
//--------------分割线
//直接导入并执行模块代码,不需要得到模块向外共享的成员
import './05_m3.js'

Pomise相关

1.回调地狱

多层回调函数的相互嵌套,就形成了回调地狱。示例代码如下:

setTimeout(()=>{  //第一层回调函数
  console.log('延时1s后输出')
  
  setTimeout(()=>{ //第二层回调函数
    console.log('再延时两秒后输出')

    setTimeout(()=>{
      console.log('再延时3秒后输出')
    }3000)
  },2000)
},1000)

回调地狱的缺点:

  • 代码的耦合性太强,牵一发而动全身,难以维护。
  • 大量的冗余的代码互相嵌套,代码的可读性变差

1.1如何解决回调地狱

为了解决回调地狱的问题,ES6(ECMAScript 2015) 中新增了Promise的概念。

1.2 promise的基本概念

1)promise是一个构造函数

  • 我们可以创建promise示例 const p = new Promise()
  • new 出来的Promise实例对象,代表一个异步操作
    2)promise.prototype 上包含一个.then()方法
  • 每一次new Prominse() 构造函数得到的实例对象,
  • 都可以通过原型链的方式访问到.then()方法,例如p.then()
    3).then() 方法用来预先指定成功和失败的回调函数
  • p.then(成功的回调函数,失败的回调函数)
  • p.then(result=>{},error=>{})
  • 调用.then()方法时,成功的回调函数是必选的、失败的回调函数是可选的
const p = new Promise({xxx})
p.then(r1=>{
  return new promise({xxx})
})
.then(r2=>{
   return new promise({xxx})
}).catch(error=>{

})
//使用catch可以捕获错误

3. Promise.all()方法

Promise.all()方法会发起并行的Promise异步操作,等所有的异步操作全部结束后才会执行下一步的.then操作(等待机制)。示例代码如下:

//1.定义一个数组,存放3个文件的异步操作
const promiseArr = [
  thenFs.readFile('./files/11.txt','utf8'),
  thenFs.readFile('./files/22.txt','utf8'),
  thenFs.readFile('./files/33.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)
})
  • 注意: 数组中Promise实例的顺序就是最终结果的顺序!

4.promise.race() 方法

promise.race() 方法会发起并行的Promise异步操作,只要任何一个异步操作完成,就立即执行下一步的.then操作(赛跑机制)。示例代码如下:

//1.定义一个数组,存放3个文件的异步操作
const promiseArr = [
  thenFs.readFile('./files/11.txt','utf8'),
  thenFs.readFile('./files/22.txt','utf8'),
  thenFs.readFile('./files/33.txt','utf8')
]
// 2.将promise的数组,作为Promise.race()的参数
Promise.race(promiseArr)
.then(result)=>{
  //2.1只要任何一个异步操作完成,就立即执行成功的回调函数(赛跑机制)
  console.log(r1,r2,r3)
})
.catch(err => {
  //2.2捕获Promise异步操作中的错误
  console.log(err.message)
})

4.1 getFile方法的基本定义

//1.方法的名称为 getFile
//2. 方法接收一个形参fpath,表示要读取的文件的路径
function getFile(fpath){
  //3.方法的返回值为 Promise的实例对象
  return new Promise()
}
  • 注意:第五行代码中的 new Promise() 只是创建了一个形式上的异步操作。

4.2创建具体的异步操作

如果想要创建具体的异步操作,则需要在new Promise() 构造函数期间,传递一个function函数,将具体的异步操作定义到function函数内部。示例代码如下:

//1.方法的名称为 getFile
//2. 方法接收一个形参fpath,表示要读取的文件的路径
function getFile(fpath){
  //3.方法的返回值为 Promise的实例对象
  return new Promise(function(){
    //4.下面这行代码,表示是一个具体的、读文件的异步操作
    fs.readFile(fpath,'utf8',(err,dataStr)=>{ })
  })
}

4.3获取.then 的两个实参

getFile('./files/1.txt').then(成功的回调函数,失败的回调函数)

4.4 调用resolve 和 reject回调函数

Promise异步操作的结果,可以调用resvole或reject回调函数进行处理。示例代码如下:

function getFile(fpath) {
  return new Promise(function(resovle,reject){
    fs.readFile(fpath,'utf8',(err,dataStr)=>{
      if(err) return reject(err)   //如果读取失败,则调用"失败的回调函数"
      resolve(dataStr)  //如果成功,则调用"成功的回调函数"
    })
  })
}
getFile('./files/1.txt').then((r1)=>console.log(r1)).catch(err=>console.log(err))

async / await

1. 什么是async/await

async/await 是ES8(ECMAScript2017) 引入的新语法,用来简化Promise异步操作。在async/await出现之前,开发者只能通过链式.then()方式处理Promise异步操作。示例代码如下:

const p = new Promise({xxx})
p.then(r1=>{
  return new promise({xxx})
})
.then(r2=>{
   return new promise({xxx})
}).catch(error=>{

})
  • .then链式调用的优点:解决了回调地狱的问题
  • .then链式调用的缺点: 代码冗余、阅读性差、不易理解

2.async/await 的基本使用

使用async/await 简化Promise异步操作的示例代码如下:

import thenFs form '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()

3.async/await 使用注意事项

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')   
//执行顺序 A B C r1 r2 r3 D

EventLoop

1.javaScript 是一门单线程的语言

javascript是一门单线程的编程语言。也就是说,同一时间只能做一件事情。
javascript的执行线程:
任务1 -------> 任务2------> 任务3 ------> 任务N
待执行的任务队列

  • 单线程执行任务队列的问题:
    如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死的问题。

2.同步任务和异步任务

为了防止某个耗时的任务导致程序假死的问题,javascript把待执行的任务分成了两类:
1)同步任务 (synchronous)

  • 又叫做非耗时任务,指的是在主线程上排队执行的那些任务
  • 只有前一个任务执行完毕,才能执行后一个任务
    2)异步任务 (asynchrounous)
  • 又叫做耗时任务,异步任务由于javacript委托给宿主环境进行执行
  • 当异步任务执行完成后,会通知javascript主线程执行异步任务的回调函数

3.同步任务和异步任务的执行过程

1)同步任务由javacript主线程次序执行
2)异步任务委托给宿主环境执行
3)已完成的异步任务对应的回调函数,会被加入到任务队列中等待执行。
4) javacript主线程的执行栈被清空后,会读取任务队列中的回调函数,次序执行
5)javacript主线程不断重复上面的第四步

宏任务和微任务

1.什么是宏任务和微任务

javascript把异步任务又做了进一步划分,异步任务又分为两类,分别是:
1)宏任务(macrotask)

  • 异步Ajax请求
  • setTimeout 、setInterval、
  • 文件操作
  • 其他宏任务
    2)微任务 (microtask)
  • Promise.then 、.catch 和 finally
  • process.nextTick
  • 其他微任务

2.宏任务和微任务的执行顺序

每一个宏任务执行完成之后,都会检查是否存在待执行的微任务,如果有,则执行完所有的微任务之后,再继续执行下一个宏任务。

宏任务和微任务都是指异步任务的执行方式。

宏任务指的是由浏览器或Node.js环境提供的异步任务,比如setTimeout、setInterval、I/O操作等。当宏任务被加入到任务队列中后,等待JavaScript引擎的执行。

微任务指的是由JavaScript引擎提供的异步任务,比如Promise、MutationObserver等。当微任务被加入到任务队列中后,会在当前宏任务执行完毕后立即执行。

执行顺序:当一个宏任务执行完毕后,JavaScript引擎会先执行所有微任务,然后再执行下一个宏任务。这意味着在同一个宏任务中,微任务总是优先于下一个宏任务执行。而不同宏任务之间的执行顺序是不确定的,取决于它们被加入到任务队列中的先后顺序

3.分析以下代码输出的顺序

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

分析:
1)先执行所有的同步任务,new Promise是个同步任务,new完后里面的function会立即执行 所以先输出2,然后同步任务4

2)再执行微任务
promise.then里面就是个微任务 所以输出3

3)再执行宏任务
setTimeout 输出 1

4.经典面试题

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

你可能感兴趣的:(web前端,javascript,前端)