前言
灵魂三问
- JS为什么是单线程的?
JS最初被设计用在浏览器中,那么想象一下,如果浏览器中的JS是多线程的。
场景描述:
那么现在有2个进程,process1 process2,由于是多进程的JS,所以他们对同一个dom,同时进行操作
process1 删除了该dom,而process2 编辑了该dom,同时下达2个矛盾的命令,浏览器究竟该如何执行呢?
- 为什么需要异步?
如果JS中不存在异步,只能自上而下执行,如果上一行解析时间很长,那么下面的代码就会被阻塞。
对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验
- 单线程又是如何实现异步的呢?
既然JS是单线程的,只能在一条线程上执行,又是如何实现的异步呢? 答案就是**是通过的事件循环(event loop)**
event loop
javascript 是一门单线程的脚本语言,也就意味着同一个时间只能做一件事,但是单线程有一个问题:一旦这个线程被阻塞就无法继续工作了,这肯定是不行的。由于异步编程可以实现非阻塞
的调用效果,引入异步编程自然就是顺理成章的事情了。
从同步异步来理解JS的执行机制
console.log(1)
setTimeout(function(){
console.log(2)
},0)
console.log(3)
- 首先判断JS是同步还是异步,同步就进入主进程,异步就进入
event table
- 异步任务在event table中注册函数,当满足触发条件后,被推入
event queue
- 同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主进程中
宏任务&微任务
Js 中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(micro tasks)
**宏任务**:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering.
**微任务**:process.nextTick, Promise的then或catch, Object.observer, MutationObserver.
Js 中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(micro tasks)。宏任务队列可以有多个,微任务队列只有一个。
当stack空的时候,就会从任务队列中,取任务来执行。浏览器这边,共分3步:
- 取一个宏任务来执行。执行完毕后,下一步。
- 取一个微任务来执行,执行完毕后,再取一个微任务来执行。直到微任务队列为空,执行下一步。
- 更新UI渲染,进入下一个loop。
只要执行栈中没有其他的js代码正在执行(即当前宏任务队列执行完成),微任务队列会立即执行。如果在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会立即被执行。
举个例子来说明当然最好不过
console.log('script start');
// 微任务 1
Promise.resolve().then(() => {
console.log('micro_task_1');
// 微任务1 执行时加入微任务队列尾部
Promise.resolve().then(() => {
console.log('micro_task_1>micro_task_1');
});
});
// 宏任务 1
setTimeout(() => {
console.log('macro_task_1');
Promise.resolve().then(() => {
console.log('macro_task_1>micro_task_1');
setTimeout(()=>{
console.log('macro_task_1>micro_task_1>macro_task_1');
})
});
Promise.resolve().then(() => {
console.log('macro_task_1>micro_task_2');
});
}, 0);
// 宏任务 2
setTimeout(() => {
console.log('macro_task_2');
}, 0);
var s = new Date();
while(new Date() - s < 50); // 阻塞50ms
// 微任务 2
Promise.resolve().then(() => {
console.log('micro_task_2');
});
// 宏任务 3
setTimeout(() => {
console.log('macro_task_3');
}, 0);
console.log('script end');
// script start
// script end
// micro_task_1
// micro_task_2
// micro_task_1>micro_task_1
// macro_task_1
// macro_task_1>micro_task_1
// macro_task_1>micro_task_2
// macro_task_2
// macro_task_3
// macro_task_1>micro_task_1>macro_task_1
- 先分析主任务script ,同步任务分别输出
script start
>script end
异步任务中包含两个微任务,三个宏任务 - 先执行微任务,执行第一个微任务
micro_task_1
的时候又产生了一个新的微任务micro_task_1>micro_task_1
,加入微任务队列尾部,再执行micro_task_2
,直到微任务队列为空, 如果微任务中嵌套宏任务,则把它加入对应宏任务队列尾部。因此顺序为micro_task_1
>micro_task_2
>micro_task_1>micro_task_1
- 进入下一个event-loop,先调用下UI render。 再取出下一个宏任务macro_task_1,以此类推。
浏览器的 Event Loop 遵循的是 HTML5 标准,而 NodeJs 的 Event Loop 遵循的是 libuv
对比nodejs
- 执行您的主代码。这里相同,遇到异步处理,就会分配给对应的队列。直到当前宏任务队列执行完毕。
- 执行当前宏任务队列中出现的所有微任务:先执行完所有nextTick(),然后在执行其它所有微任务。
总结:
- 每个
macroTask
队列中的macroTask
按顺序执行,在每个macroTask
之间渲染页面 - 一个
macroTask
执行结束(即js执行栈中为空),会立即处理macroTask
执行过程中产生的microTask
并且按顺序执行。microTask
产生的macroTask
会自动加入相应的宏任务队列。 - 每次loop会把这次宏任务产生的所有微任务执行完,在进行下一次loop。
附录
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/?utm_source=html5weekly
来源:CSDN
作者:qq_28147749
链接:https://blog.csdn.net/qq_28147749/article/details/103991196