模块化相关
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.同步任务和异步任务的执行过程
在JavaScript中,任务可以分为同步任务和异步任务。它们的执行过程有所不同:
同步任务执行过程:
- JavaScript是单线程的,同步任务按照顺序依次执行,每个任务执行完成后才会执行下一个任务。
- 当遇到一个同步任务时,JavaScript引擎会立即执行该任务,并阻塞后续代码的执行,直到该任务完成。
- 同步任务执行期间,页面的渲染和用户交互都会被阻塞,直到任务执行完成。
异步任务执行过程:
- 异步任务不会阻塞后续代码的执行,而是在后台执行,并在特定条件满足时触发回调函数的执行。
- 当遇到一个异步任务时,JavaScript引擎会将其放入任务队列中,继续执行后续代码而不等待异步任务完成。
- 当主线程的同步任务执行完成后,JavaScript引擎会检查任务队列,如果有异步任务已经完成,就将其对应的回调函数放入微任务队列或宏任务队列中,等待执行。
- 微任务队列中的任务会在当前任务执行完成后立即执行,而宏任务队列中的任务则需要等待下一次事件循环开始时执行。
- 当执行微任务队列中的任务时,如果又产生了新的微任务,会继续添加到微任务队列中,直到微任务队列为空。
- 当执行宏任务队列中的任务时,如果又产生了新的宏任务,会添加到宏任务队列的末尾,等待下一次事件循环开始时执行。
通过这种方式,JavaScript可以处理异步操作,避免阻塞页面渲染和用户交互。常见的异步任务包括定时器回调、网络请求、事件处理等。
宏任务和微任务
1.什么是宏任务和微任务
javascript把异步任务又做了进一步划分,异步任务又分为两类,分别是:
1)宏任务(macrotask)
- 异步Ajax请求
- setTimeout 、setInterval、
- 文件操作
- 其他宏任务
2)微任务 (microtask)
在JavaScript中,微任务是指在当前任务执行完成后立即执行的任务。它们通常用于处理异步操作的回调函数。以下是一些与JavaScript微任务相关的内容:
- Promise:Promise是JavaScript中处理异步操作的一种机制。通过Promise对象,可以将异步操作封装成一个任务,并在任务完成后执行相应的回调函数。Promise的回调函数会作为微任务在当前任务执行完成后立即执行。
- async/await:async/await是ES2017引入的一种语法糖,用于更方便地处理异步操作。通过async函数和await关键字,可以以同步的方式编写异步代码。在使用await关键字等待一个Promise对象时,后续代码会被封装成微任务,在当前任务执行完成后立即执行。
- MutationObserver:MutationObserver是一个用于监听DOM变化的API。它可以观察DOM树的变化,并在变化发生后执行回调函数。MutationObserver的回调函数会作为微任务在当前任务执行完成后立即执行。
- queueMicrotask():queueMicrotask()是一个用于将任务添加到微任务队列的函数。它接受一个回调函数作为参数,并将该回调函数添加到微任务队列中。添加的任务会在当前任务执行完成后立即执行。
这些是JavaScript中与微任务相关的一些内容。通过使用这些机制和API,可以更好地处理异步操作,并在需要时执行相应的回调函数。
2.宏任务和微任务的执行顺序
每一个宏任务执行完成之后,都会检查是否存在待执行的微任务,如果有,则执行完所有的微任务之后,再继续执行下一个宏任务。
宏任务和微任务都是指异步任务的执行方式。
宏任务指的是由浏览器或Node.js环境提供的异步任务,比如setTimeout、setInterval、I/O操作等。当宏任务被加入到任务队列中后,等待JavaScript引擎的执行。
微任务指的是由JavaScript引擎提供的异步任务,比如Promise、MutationObserver等。当微任务被加入到任务队列中后,会在当前宏任务执行完毕后立即执行。
执行顺序:当一个宏任务执行完毕后,JavaScript引擎会先执行所有微任务,然后再执行下一个宏任务。这意味着在同一个宏任务中,微任务总是优先于下一个宏任务执行。而不同宏任务之间的执行顺序是不确定的,取决于它们被加入到任务队列中的先后顺序。
当前任务
在JavaScript中,"当前任务"指的是当前正在执行的任务或代码块。JavaScript是单线程的,一次只能执行一个任务。任务可以是同步任务或异步任务。
同步任务是按照顺序依次执行的任务,每个任务执行完成后才会执行下一个任务。例如,函数调用、循环、条件语句等都是同步任务。
异步任务是在后台执行的任务,不会阻塞后续代码的执行。当遇到异步任务时,JavaScript引擎会将其放入任务队列中,并继续执行后续代码。异步任务的执行通常是由事件触发、定时器到期、网络请求返回等条件触发的。当主线程的同步任务执行完成后,JavaScript引擎会检查任务队列,如果有已完成的异步任务,就将其对应的回调函数放入微任务队列或宏任务队列中,等待执行。
因此,当前任务可以是正在执行的同步任务,也可以是正在等待执行的异步任务。微任务队列中的任务会在当前任务执行完成后立即执行,而宏任务队列中的任务则需要等待下一次事件循环开始时执行。
下一次事件循环
下一次事件循环是指在JavaScript中,当当前任务执行完成后,JavaScript引擎开始执行下一个任务的过程。事件循环是JavaScript的执行模型,用于处理异步任务和事件回调。
事件循环的过程包括以下几个步骤:
- 执行当前任务:JavaScript引擎执行当前任务,这可以是同步任务或异步任务。
- 检查微任务队列:在当前任务执行完成后,JavaScript引擎会立即检查微任务队列。如果微任务队列中有任务,引擎会按照队列中的顺序依次执行这些微任务。
- 更新渲染:如果当前任务对页面进行了修改,JavaScript引擎会通知浏览器进行页面的重新渲染。
- 检查宏任务队列:在微任务执行完毕后,JavaScript引擎会检查宏任务队列。如果宏任务队列中有任务,引擎会选择其中的一个任务执行。
- 执行宏任务:JavaScript引擎执行选中的宏任务,这可以是定时器回调、事件回调、网络请求回调等。
- 回到步骤2:在宏任务执行完成后,JavaScript引擎会回到步骤2,继续检查微任务队列并执行微任务,然后再次检查宏任务队列。
这个过程会不断循环,直到微任务队列和宏任务队列都为空。这样可以确保异步任务和事件回调按照正确的顺序执行,并且在每个任务执行之间允许浏览器进行页面渲染和用户交互响应。
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