EventLoop 解决了什么问题(背景)?
JavaScript单线程是一门单线程的语言,同一个时间只能做一件事情,如果当一个语句需要执行很长时间的话(比如:请求、定时器、读取文件等等),后面的语句就得一直等着前面的语句执行结束后才会开始执行。因此,JavaScript将所有执行任务分为了同步任务和异步任务。而Event Loop就是浏览器或Node解决JS单线程运行时不会阻塞的一种机制
什么是同步任务和异步任务
同步任务:又叫做非耗时任务,指的是在主线程上排队执行的那些任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务:又叫做耗时任务,异步任务由JavaScript 委托给宿主环境进行执行,当异步任务执行完成后,会通知JavaScript 主线程执行异步任务的回调函数
什么是宏任务和微任务
JavaScript 把异步任务又做了进一步的划分,异步任务又分为两类:宏任务和微任务
宏任务:当前调用栈执行的任务
微任务:需要在此次宏任务执行结束后,下一次宏任务执行前,执行的任务
宏任务 | 微任务 |
---|---|
定时器(setTimeout、setInterval) | Promise(then、catch、finally) |
setImmediate | MutationObserver |
requestAnimationFrame | process.nextTick(Node) |
script标签下整块代码 |
事件循环的进程模型
- 从宏任务队列中,按照入队顺序,找到第一个执行的宏任务,放入调用栈,开始执行
- 执行遇到异步任务,如果是微任务,将放到微任务队列中,如果是宏任务,则放到宏任务队列中
- 同步任务执行完成后(即调用栈清空后),该宏任务被推出宏任务队列,然后微任务队列开始按照入队顺序,依次执行其中的微任务,直至微任务队列清空为止
- 当微任务队列清空后,一个事件循环结束
- 接着从宏任务队列中,找到下一个执行的宏任务,开始第二个事件循环,直至宏任务队列清空为止
常见面试题
// 整体代码是一个宏任务,放入到宏任务队列中,进入调用栈执行
console.log(1);
setTimeout(()=>{
console.log(2);
Promise.resolve().then(res => { console.log(3) });
setTimeout(()=>{
console.log(4);
Promise.resolve().then(res => { console.log(5) });
}, 0)
}, 30)
Promise.resolve().then(res=>{ console.log(6) })
const fn = async () => {
console.log(7)
// 遇到了await 会等待promise完成,同时交出执行权
await new Promise((resolve)=>{
// new Promise 中的代码立即执行
console.log(8)
setTimeout(()=>{
resolve();
console.log(9)
}, 20)
}).then(res => console.log(10));
console.log(11)
}
// fn执行
fn();
setTimeout(()=>{
console.log(12);
new Promise((resolve) => {
console.log(13)
resolve();
}).then(res => { console.log(14) })
}, 0)
// 打印结果:1 7 8 6 12 13 14 9 10 11 2 3 4 5