JS学习笔记 - 深入理解浏览器和Node.js中的宏任务、微任务、事件循环
JavaScript是单线程的
JavaScript是一个单线程的脚本语言,即JS的代码只能在一个进程中运行。也就是说,JS只能同时执行一个任务。但如果全部代码全是同步执行的,这会引发很严重的问题,比方说我们要从远端获取一些数据,难道要一直循环代码去判断是否拿到了返回结果么?
于是就有了异步事件的概念,注册一个回调函数,比如说发一个网络请求,我们告诉主程序等到接收到数据后通知我,然后我们就可以去做其他的事情了。
然后在异步完成后,会通知到我们,但是此时可能程序正在做其他的事情,所以即使异步完成了也需要在一旁等待,等到程序空闲下来才有时间去看哪些异步已经完成了,可以去执行。
浏览器线程简介
GUI渲染线程
负责渲染浏览器界面HTML元素
JS引擎线程
负责处理JavaScript脚本主程序
定时器触发线程
定时器setInterval
与setTimeout
所在线程
浏览器事件线程
用来控制事件,如触发的onload、click事件等
http请求线程
在XMLHttpRequest
在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript引擎的任务队列中等待处理
宏任务和微任务
宏任务和微任务有哪些
宏任务
setTimeout
setInterval
setImmediate
(Node.js)requestAnimationFrame
(浏览器)UI Rendering
(浏览器)I/O
script
微任务
Promise.then() Promise.catch() Promise.finally()
MutationObserver
Object.observe
async await
process.nextTick
(Node)
浏览器的事件循环
同步和异步任务分别进入不同的执行“场所”,同步进入主线程,异步进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入任务队列(Event Queue)。主线程内的任务执行完毕为空,就去任务队列(Event Queue)读取对应的函数,进入主线程执行。
浏览器的宏任务、微任务机制
JS异步还有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入任务队列(event queue),然后再执行微任务,将微任务放入微任务队列(micro task queue),但是,这两个queue不是一个queue
执行顺序
主线程 >> 主线程上创建的微任务 >> 主线程上创建的宏任务 >> 主线程 >> 主线程上创建的微任务 >> 主线程上创建的宏任务 ……
示例
console.log(1)
setTimeout(()=>{
console.log(2)
},0)
new Promise((resolve,reject)=>{
console.log(3)
resolve()
}).then(res=>{
console.log(4)
})
console.log(5)
setTimeout(()=>{
new Promise((resolve,reject)=>{
console.log(6)
}).then(res=>{
console.log(7)
})
},0)
console.log(8)
/*
控制台输出:
1
3
5
8
4
2
6
/// 7不输出因为该Promise状态仍为pending,且无法变为resolved
*/
Node.js的事件循环机制
Event Loop各阶段描述
阶段 | 描述 |
---|---|
timers | 用于处理setTimeout 和setInterval 的回调函数 |
pending callbacks | 执行某些系统操作的回调 |
idle,prepare | Node内部使用,忽略 |
poll | 等待新I/O事件触发,以及执行I/O相关回调 |
check | 当poll阶段执行完成会进入到check阶段执行,该阶段的执行内容是所有setImmediate 回调 |
close callbacks | socket的异常关闭,close 事件的回调会在该阶段执行 |
Node中的宏任务和微任务队列
宏任务队列
- timers queue
- pending callbacks queue
- check queue
- close queue
微任务队列
- next Tick Queue
- other Micro Queue
poll 轮询阶段
poll是一个核心阶段,等新I/O事件的触发,以及执行I/O相关回调
主要功能:
- 执行到期的
setTimeout
和setInterval
的回调函数 - 处理poll队列中的事件
工作机制:当没有timers被调度,分两种情况:
- 如果poll队列不为空,会挨个执行队列里的callback,直到队列为空,或达到系统限制
- 如果poll队列为空,分两种情况:
- 如果执行了
setImmediate
,eventLoop
会结束poll阶段,进入到check阶段执行 - 如果没有执行
setImmediate
,eventLoop
会等待callback进入队列
- 如果执行了
一旦poll队列为空,
evetloop
会检查timers,如果计时已到,event loop 会回到 timers 阶段,执行相应的回调函数.
执行顺序
timers
- nextTickQueue
- otherMicroQueue
pending callbacks
- nextTickQueue
- otherMicroQueue
idle,prepare
- nextTickQueue
- otherMicroQueue
poll
(可能返回到timers阶段)- nextTickQueue
- otherMicroQueue
check
- nextTickQueue
- otherMicroQueue
close callbacks
示例
console.log(0);
setTimeout(() => {
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
setImmediate(() => {
console.log(3);
})
process.nextTick(() => {
console.log(4);
})
}, 0);
setImmediate(() => {
console.log(5);
process.nextTick(() => {
console.log(6);
})
})
setTimeout(() => {
console.log(7);
process.nextTick(() => {
console.log(8);
})
}, 0);
process.nextTick(() => {
console.log(9);
})
console.log(10);
/* Node.js 控制台输出:
0
10
9
1
4
7
8
5
6
3
2
*/