一、考察的是什么?
当面试官询问,setTimeout,promise,async/await的时候实际是在考察候选人对于js事件循环(Event loop),微任务(microtask)和宏任务(macrotasks)的理解。
众所周知,js是单线程语言,所以在执行任务的时候需要一个个来执行。
js在执行任务的时候会执行栈操作,执行过程中遇到同步任务会将此任务的上下文push到调用栈中(先进后出),碰到异步任务会将异步任务放入队列(先进先出)中。
而js的异步任务又分为两类,微任务和宏任务。接下来我们就了解一下什么是宏任务什么又是微任务。
二 、宏任务
宏任务包含哪些呢?
- script(整体代码)
- setTimeout
- setInterval
- I/O
- UI交互事件,click,mouseover,mouseleave,alert()等
- MessageChannel
- setImmediate(Node.js 环境)
三、微任务
微任务包含哪些呢?
- Promise.then() .catch() .finnaly()
- Object.observe
- MutationObserver
- process.nextTick(Node.js 环境)
⚠️这里我们需要特别注意一下,Promise是同步操作,只有当.then(), resove(),.catch()等操作后才会进入微任务。
四、事件循环
那么在宏任务队列和微任务队列中,又是按照什么顺序进行执行的呢?
在js的执行中,会优先进行同步操作即调用栈的操作(但是宏任务script执行优先级是高于其他的),清空调用栈之后,会优先执行微任务,当微任务执行完毕,会判断是否有新的渲染如果有的话等渲染结束,如果有新的微任务则执行微任务,否则就执行宏任务。在执行宏任务的过程中如果有新的微任务,那么去优先执行新的微任务,微任务队列执行完毕后。继续判断是否有新的渲染,在执行宏任务。。。。知道宏任务队列执行完毕。
上述的操作就是在事件循环中完成的。
js和node的执行流程都是基于事件循环。
事件循环一般都是在等待任务(休眠),执行任务中进行切换。当执行任务的时候,页面不会被渲染。
五、 async await
在上面中已经了解到setTimeout和promise的区别(一个是宏任务,一个是微任务)。那么async await又是怎么回事呢?
async是AsyncFunction构造函数的实例,其中允许使用关键字await。
⚠️async无论使用没使用await都会返回一个Promise。
async function foo() {
return 1
}
foo();
//Promise {<fulfilled>: 1}
//promise 的 [[Prototype]]: Promise
//promise 的 [[PromiseState]]: "fulfilled"
//promise 的 [[PromiseResult]]: 1
❓当我们使用await 关键字的时候会发生什么
await 关键字会暂停整个async函数的运行,并让出控制权,只有当等待的机遇promise的异步操作被兑现或者被拒绝之后才会恢复进程。 promise的解决值被当作该await的表达式的返回值。
⚠需要注意的是async 实现异步是因为由await 进行了拦截,跳出了当前任务。
可以说async的函数体,可以看作0个或者多个await表达式构成,从第一行直到第一个await表达式(如果有)都是同步运行,直到遇到await。也可以说如果函数体有一个await,async就一定会异步,没有的话就是同步。
async function foo() {
await 1
}
//等价于
function foo() {
return Promise.resolve(1).then(() => undefined)
}
⚠️做题的时候可以将async await 转换成等价的promise 然后用事件循环去判断执行哪一部。
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
async1()
// 上述等价于
async function async1() {
console.log('async1 start');
new Promise((resolved)=>{
async2();
resolved();
}).then(()=>{
console.log('async1 end');
})
}
六、案例
上面我们了解到了,事件循环,微任务,宏任务以及async和await。那么我们来做一道有关setTimeout和Promise以及async、await的面试题,来看看掌握的怎么样。
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');
我们可以将上述async 和 await 的代码转换成等价promise
async function async1() { //1
console.log('async1 start'); //2
new Promise((resolved)=>{ //3
async2(); //4
resolved();//5
}).then(()=>{//6
console.log('async1 end');//7
})//8
}//9
async function async2() {//10
return new Promise(function() {//11
console.log('async2');//12
}).then(()=> undefined)//13
}//14
console.log('script start');//15
setTimeout(function() {//16
console.log('setTimeout');//17
}, 0)//18
async1();//19
new Promise(function(resolve) {//20
console.log('promise1');//21
resolve();//22
}).then(function() {//23
console.log('promise2');//24
});//25
console.log('script end');//26
解析
- 开始执行script执行的宏任务
- 15 行 同步任务 执行 打印 script start
- 执行async1()
- 2 行是同步任务 打印 async1 start
- 因为await 必须执行 4行 async2()
- 12 行是同步任务打印 async2
- 回到async1
- 5行resoved() 放入微任务队列
- 执行20行的Promise,21是同步 打印 promise1
- 22行resolve() 放入微任务队列
- 执行26行 打印script end
- script所形成的宏任务执行完毕
- 按照先入先出执行微任务队列
- 5行的resoved() 执行 打印 第7行的 async1 end
- 22行的resoved() 执行 打印 24行的 promise2
- 微任务队列执行完毕,页面没有渲染,执行宏任务队列
- (只有setTimeout)17行 执行 打印 setTimeout
- 宏任务队列清空
所以结果是
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
七、总结
- setTimoout 就在script标签执行结束后才会执行,属于宏任务队列
- Promise 本身是同步任务,只有resove() ,then()等才会加入微任务队列
- async 函数中在await关键字之前都是同步任务
- 计算时可以将async 转换成等价的promise去运算
- 事件循环执行顺讯 :script宏任务—> 同步任务(调用栈)–> 优先微任务队列–>页面是否渲染?–>有微任务产生优先微任务否则宏任务
本文探讨了JavaScript事件循环的工作原理,讲解了宏任务(如setTimeout、script标签)与微任务(Promise、async/await)的区别,通过案例解析展示了如何在面试中应用这些概念。
1560

被折叠的 条评论
为什么被折叠?



