事件循环和生命周期
js是单线程主要说的是 js引擎线程只有一个主线程这个线程跟GUI线程是互斥的
GUI线程是绘制html css js也可修改这部分所以互斥
https://segmentfault.com/a/1190000023315304
https://www.jianshu.com/p/71544067d3a3
https://zhuanlan.zhihu.com/p/35918797
microTskQueue 微任务队列
nextTickQueue 即将执行的任队列
图上可知
nextTickQueue
和microTaskQueue
,它们不是循环的一部分,它们的回调可以在任意阶段执行。它们有更高的优先级去执行。nextTickQueue 的优先级高于 microTaskQueue。
timers: 执行
setTimeout
和setInterval
的回调 内层嵌入新的setTimeout
放到下个timers执行pending callbacks: 执行 I/O, 回调推迟到下一个循环 迭代
idle, prepare: 仅系统内部使用
poll: 检索新的 I/O 事件;执行与 I/O 相关的回调。事实上除了其他几个阶段处理的事情,其他几乎所有的异步都在这个阶段处理。
check:
setImmediate
在这里执行close callbacks: 一些关闭的回调函数,如:
socket.on('close', ...)
每个阶段都有自己先进先出的队列,只有当这个队列的事件执行完或者达到该阶段的上限时,才会进入下一个阶段。
当个v8引擎将js代码解析后传入libuv引擎后,循环首先进入poll阶段
另一个版本理解对比
定时器(Timer)阶段
这个是事件循环开始的阶段,绑定到这个阶段的队列,保留着定时器(setTimeout, setInterval)的回调。尽管它并没有将回调推入队列中,但是用最小堆来存储计时器并且在到达规定的时间后执行回调。
即将发生的(Pending) i/o 回调阶段
这个阶段执行在事件循环中 pending_queue
里的回调。这些回调是被之前的操作推入的。例如当你尝试往 tcp 中写入一些东西,这个工作完成了,然后回调被推入到队列中。错误处理的回调也在这里。
Idle, Prepare 阶段
尽管名字是空闲(idle),但是每个循环(tick)都运行。Prepare 也在轮询阶段开始之前运行。不管怎样,这两个阶段是 node 主要做一些内部操作的阶段;因此,我们不在这儿讨论。
轮询(Poll)阶段
可能整个事件循环最重要的一个阶段就是 poll phase
。这个阶段接受新传入的连接(新的 Socket 建立等)和数据(文件读取等)。我们可以将轮询阶段分成几个不同的部分。
- 如果
watch_queue
(这个队列被绑定到轮询阶段)不为空,它们将会被一个接着一个的执行直到队列为空或者系统到达最大的限制。 - 一旦队列为空,node 就会等待新的连接。等待或者睡眠的时间取决于多种因素,直到有一个i/o事件返回,循环会进入i/o callback阶段并立即执行这个事件的callback。
检查(Check)阶段
轮询的下一个阶段是 check phase
,这个专用于 setImmediate
的阶段。为什么需要一个专门的队列来处理 setImmediate 回调。这是因为轮询阶段的行为,待会儿将在流程部分讨论。现在只需要记住检查(check)阶段主要用于处理 setImmediate() 的回调。
关闭(Close)回调
回调的关闭(socket.on(‘close’, ()=>{})) 都在这里处理的,更像一个清理阶段
nextTickQueue & microTaskQueue(微任务队列)
nextTickQueue 中的存储着被 process.nextTick()
触发的回调。microTaskQueue 保留着被 Promise 触发的回调。它们都不是事件循环的一部分(不是在 libUV 中开发的),而是在 node.js 中。在 C/C++ 和 Javascript 有交叉的时候,它们都是尽可能快地被调用。因此它们应该在当前操作运行后(不一定是当前 js 回调执行完)。process.nextTick()
nextTick queue。这个队列中的回调执行虽然没有被表示为一个阶段,当时这些事件却会在每一个阶段执行完毕准备进入下一个阶段时优先执行。当事件循环准备进入下一个阶段之前,会先检查nextTick queue中是否有任务,如果有,那么会先清空这个队列。与执行poll queue中的任务不同的是,这个操作在队列清空前是不会停止的。这也就意味着,错误的使用process.nextTick()方法会导致node进入一个死循环。。直到内存泄漏。