一、Event loop引入
1、js为什么是单线程的?
之前我们已经学习了浏览器的关键渲染路径,知道了网页内容交给浏览器后浏览器是如何解析、渲染、绘制的,也提到了浏览器是多线程的,js是单线程的,那么为什么js就是单线程的呢?通过反证法如果js是多线程,多个js任务同时更新同一个dom,一个删除一个修改,这时候浏览器就不知道该如何执行了,故js是单线程的。
2、js为什么需要异步任务?
如果 JavaScript 中不存在异步,只能自上而下执行,如果上一行解析时间很长,那么下面的代码就会被阻塞。对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验,所以 JavaScript 中存在异步执行。
3、什么是Event loop
既然js是单选程的,他要如何执行异步任务呢?因此浏览器通过事件循环机制来解决。那么什么是事件循环机制呢?事件循环的概念非常简单。有一个无休止的循环,JavaScript引擎等待任务,执行任务,然后休眠,等待更多任务。接下来我们以浏览器执行js代码为例,讲解什么是事件循环。
二、Event loop
1、总览
①JS本身是没有办法执行异步任务的,需要环境的支持(如浏览器)
②JS有两个任务队列(宏任务和微任务)和一个任务执行栈,执行栈只能放一个宏任务或者微任务,执行完这个任务后清空执行栈。
③当主线程中js代码全部执行完毕后,这时js解析器会从任务队列中取异步任务来进行执行,根据优先级先取出宏任务执行(一次只取一个执行),执行完毕后,去执行所有的微任务,然后再去取下一个宏任务完成后再去取所有微任务,重复执行上述操作,这种机制就是事件循环。
2、常见的微任务和宏任务
- 常见的微任务包括: promise 的回调、await(本质也是Promise)、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。
- 常见的宏任务包括: script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲染等。
3、代码演示
<script>
function fn(){
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
Promise.resolve().then(() => {
console.log(6)
}).then(() => {
console.log(7)
setTimeout(() => {
console.log(8)
}, 0);
});
})
setTimeout(() => {
console.log(9);
})
console.log(10);
}
fn();
<script>
①第一次事件循环
- script入宏队列
MacroTask Queue(宏任务) | MicroTask Queue(微任务) | 输出 |
<script>....</script> |
- script入栈
MacroTask Queue(宏任务) | MicroTask Queue(微任务) | Call Stack |
<script>....</script> |
- 开始执行同步代码
步骤①: console.log(1);
步骤②: setTimeout入宏队列
步骤③: 执行promise构造函数,promise.then()之后的代码入微任务队列
步骤④: setTimeout入宏队列
步骤⑤:console.log(10);
MacroTask Queue(宏任务) | MicroTask Queue(微任务) | 输出 |
setTimeout(() => { console.log(2); Promise.resolve().then(() => { console.log(3) }); }); setTimeout(() => { console.log(9); }) | then((data) => { console.log(data); Promise.resolve().then(() => { console.log(6) }).then(() => { console.log(7) setTimeout(() => { console.log(8) }, 0); }); }) | 1 4 10 |
- 执行微任务1
步骤①:console.log(data);//data为5
步骤②: 执行promise构造函数,promise.then()之后的代码入微任务队列
MacroTask Queue(宏任务) | MicroTask Queue(微任务) | 输出 |
setTimeout(() => { console.log(2); Promise.resolve().then(() => { console.log(3) }); }); setTimeout(() => { console.log(9); }) | then(() => { console.log(7) setTimeout(() => { console.log(8) }, 0); }); | 1 4 10 5 6 |
- 继续执行微队列
步骤①:console.log(7)
步骤②:setTimeout入宏队列
MacroTask Queue(宏任务) | MicroTask Queue(微任务) | 输出 |
setTimeout(() => { console.log(2); Promise.resolve().then(() => { console.log(3) }); }); setTimeout(() => { console.log(9); }) setTimeout(() => { console.log(8) }, 0) | 1 4 10 5 6 7 |
至此第一遍事件循环结束剩下的需要你自己分析喽!
根据分析输出结果为:
输出 |
1 4 10 5 6 7 2 3 9 8 |
完结撒花!
参考文章: