目录
1.单线程
什么是单线程?
2.同步和异步
同步
异步
3.事件循环(EventLoop)
1.事件循环的基本概念
2.微任务/宏任务
3.宏任务和微任务的执行顺序
4.常见的面试题
首先我们需要明白JS是单线程的,这是为了降低程序复杂性,但同时为了多个事件能同时被处理,JS提供了异步的处理方式(其实JS本身是没有异步这一说法的,都是由执行环境所提供的)
就行马路的单向通行道一样,一排车只能一辆一辆的排队前行通过。
程序同一时间只做一件事! 场景如下:
同步阻塞的概念指的是A调用B,B执行完获得结果返回给A,A在等待B执行过程中是停滞的状态,直到拿到结果才会往下执行
异步与同步一样,其实也是一种消息通知机制,引用上面的比方,A调用B,无需等待B的结果,A继续往下执行,的同时B也在执行,B通过状态、通知等方式来通知A或者走回调来处理结果,做一件事的时候不用等待事情的结果,继续往下走,等有了结果再来通知我
Tips:在js执行同步任务和异步任务的过程中,在主线程执行栈中的同步任务执行完毕就会,读取任务队列中的回调函数,将回调函数放到主线程执行栈中执行,执行完毕又会继续读取任务队列中的回调函数,放到主线程执行栈中执行,如此往复的过程称为事件循环(EventLoop);
Tips:以上内容已经讲解了同步任务和异步任务的概念,javascript将异步任务又进一步划分成了宏任务和微任务;
常见的宏任务有:Ajax请求、文件操作、setTimeout、setInterval、Dom操作、、
常见的微任务: Promise的then、catch、finally方法,Process.nextTick,mutationObserver;
在每一个宏任务执行完毕后就会去查找是否存在微任务,如果微任务存在就将微任务执行完毕再去执行下一个宏任务,以此往复,直到所有的任务执行结束
经典面试题
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start')
setTimeout(function(){
console.log('setTimeout')
},0)
async1();
new Promise(function(resolve){
console.log('promise1')
resolve();
}).then(function(){
console.log('promise2')
})
console.log('script end')
⛳️ 分析过程
1.定义一个异步函数 async1
2.定义一个异步函数 async2
3.打印 ‘script start’ // *1
4.定义一个定时器(宏任务,优先级低于微任务),在0ms 之后输出
5.执行异步函数 async1
- 打印 'async1 start' // *2
- 遇到await 表达式,执行 await 后面的 async2 //关键的地方,await后面的代码会阻塞 ,async2执行完毕后,后面的代码类似于传入then()中的回调
- 打印 'async2' // *3
- 返回一个 Promise,跳出 async1 函数体
6.执行 new Promise 里的语句
- 打印 ‘promise1‘ // *
- resolve() , 返回一个 Promise 对象,把这个 Promise 压进队列里
7.打印 ’script end’ // *
8.同步栈执行完毕
9.回到 async1 的函数体,async2 函数没有返回 Promise,所以把要等async2 的值 resolve,把 Promise 压进队列
10.执行 new Promise 后面的 .then,打印 ’promise2‘ // *6
11.回到 async1 的函数体,await 返回 Promise.resolve() ,然后打印后面的 ’async1 end‘ // *7
12.最后执行定时器(宏任务) setTimeout,打印 ’setTimeout‘ // *8
⛳️ 答案:
- script start
- async1 start
- async2
- promise1
- script end
- promise2
- async1 end
- setTimeout
async2改造一下
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}
function async2(){ // 去掉了 async 关键字
console.log('async2');
}
console.log('script start')
setTimeout(function(){
console.log('setTimeout')
},0)
async1();
new Promise(function(resolve){
console.log('promise1')
resolve();
}).then(function(){
console.log('promise2')
})
console.log('script end')
⛳️ 后续结论
- script start
- async1 start
- async2
- promise1
- script end
- async1 end
- promise2
- setTimeout
console.log("script start")
new Promise((resolve) => {
console.log("promise1")
resolve('微任务1')
})
.then((data) => { //第一个Promise加入微任务列表
console.log(data)
})
async function fun1() {
console.log("fun1")
let data = await fun2() //关键的地方,await后面的代码会阻塞 ,fun2执行完毕后,后面的代码类似于传入then()中的回调
console.log(data)
console.log("await堵塞")
}
setTimeout(() => {
console.log("setTimeout 宏任务") //一个宏任务,加入宏任务队列
}, 0)
async function fun2() {
console.log("fun2")
// return "no promise"
return new Promise((resolve, reject) => {
resolve("fun2 微任务")
})
// return "await"
}
fun1() //代码开始执行
new Promise((resolve) => {
console.log("promise2")
resolve("微任务2")
})
.then((data) => { //promise状态已经fulfilled,直接加入微任务队列
console.log(data)
})
console.log("同步end") // 同步代码结束
⛳️ 上面代码,执行结果(这种情况是: await后面的异步函数return的是一个Promise)
- script start
- promise1
- fun1
- fun2
- promise2
- 同步end
- 微任务1
- 微任务2
- fun2 微任务
- await堵塞
- setTimeout 宏任务
console.log("script start")
new Promise((resolve) => {
console.log("promise1")
resolve('微任务1')
})
.then((data) => { //第一个Promise加入微任务列表
console.log(data)
})
async function fun1() {
console.log("fun1")
let data = await fun2() //关键的地方,await后面的代码会阻塞 ,fun2执行完毕后,后面的代码类似于传入then()中的回调
console.log(data)
console.log("await堵塞")
}
setTimeout(() => {
console.log("setTimeout 宏任务") //一个宏任务,加入宏任务队列
}, 0)
async function fun2() {
console.log("fun2")
return "no promise"
// return new Promise((resolve, reject) => {
// resolve("fun2 微任务")
// })
// return "await"
}
fun1() //代码开始执行
new Promise((resolve) => {
console.log("promise2")
resolve("微任务2")
})
.then((data) => { //promise状态已经fulfilled,直接加入微任务队列
console.log(data)
})
console.log("同步end") // 同步代码结束
⛳️ 上面代码,执行结果(这种情况是: await后面的异步函数return的不是Promise)
- script start
- promise1
- fun1
- fun2
- promise2
- 同步end
- 微任务1
- no promise
- await堵塞
- 微任务2
- setTimeout 宏任务
⚠️ 需要注意的点
- 从结果上来看,await后面的异步函数return的是否是Promise,执行顺序的不同点在于await后面的代码执行是在微任务2之前还是之后执行,也就是微任务队列顺序的问题
- 当return的是Promise时:await后面的代码会在微任务2之后执行
- 当return的不是Promise时:await后面的代码会在微任务2之前执行
⚠️ 当return的不是promise时
- async声明的异步函数会自动将返回包装成promise ,到调用fun2这个异步函数时,await就可以看做是使用then方法,会将回调函数(这里就是await后面的代码) 放入微任务队列,这里是排在微任务一之后,微任务二之前,所以这里await后面的代码会在微任务2之前执行
⚠️ 当return的是promise时
- 当resolve的参数是一个promise时,此时会将这个参数promise放入微任务队列(此时排在微任务1之后),而他的父级promise状态不会改变,等到微任务队列中执行到这个promise时,父promise的then方法绑定到子promise上,所以这就解释了上面的执行顺序的问题