# 事件循环
https://www.imooc.com/article/27083?block_id=tuijian_wz
https://zhuanlan.zhihu.com/p/34182184
https://zhuanlan.zhihu.com/p/54882306
https://zhuanlan.zhihu.com/p/33058983
setimmediate 将任务注册到下一个check阶段,而不是当前的check阶段
const promise = Promise.resolve()
.then(() => {
return promise
})
promise.catch(console.error)
// 会导致死循环
9 5 8 1 4 7 3 6 2
执行一个宏任务执行一对微任务,而不是执行完一个阶段再执行微任务?
# Node事件循环

# 执行流程概览
- 执行脚本,初始化事件循环
- 开始执行事件循环:
- Timers: setTimeout, setInterval (比较当前时间和注册时间,执行所有过期的任务)
- Pending IO: 其他的IO回调
- Idle,prepare: 忽略
- Poll:文件IO、服务器IO等
- Check:setImmediate (执行所有当前Tick下的任务)
- Close: on('close', fn)
- 下一个事件循环
# 宏任务的分类
setTimeout, setInterval
setImmediate
IO 回调
# 微任务的分类
process.nextTick
Promise.then
# 微任务的执行时机
- 当一个宏任务执行完毕后立即检查微任务队列,并执行所有微任务(没有限制)
- 微任务的执行顺序:先执行process.nextTick任务,再执行promise任务
# setImmediate特性
setImmediate相比于setTimeout而言少了一个时间比较的过程,执行更快(性能快出50倍左右,可以说是出于性能考虑使用setImmediate替代setTimeout(fn, 0))。
setImmediate置于Poll阶段之后,对于IO回调可以尽快的执行。
setImmediate会将现有的队列任务一次性读取到临时队列执行,所以在setImmediate中递归setImmediate不会阻塞JS执行。(见源码中QUEUE_MOVE方法)
uv__run_check(loop); // 在每一轮循环中执行该函数,执行时机见uv_run void uv__run_##name(uv_loop_t* loop) { \ uv_##name##_t* h; \ QUEUE queue; \ QUEUE* q; \ // 把该类型对应的队列中所有节点摘下来挂载到queue变量 QUEUE_MOVE(&loop->name##_handles, &queue); \ // 遍历队列,执行每个节点里面的函数 while (!QUEUE_EMPTY(&queue)) { \ // 取下当前待处理的节点 q = QUEUE_HEAD(&queue); \ // 取得该节点对应的整个结构体的基地址 h = QUEUE_DATA(q, uv_##name##_t, queue); \ // 把该节点移出当前队列 QUEUE_REMOVE(q); \ // 重新插入原来的队列 QUEUE_INSERT_TAIL(&loop->name##_handles, q); \ // 执行回调函数 h->name##_cb(h); \ } \ }只要在check阶段之前加入队列的任务都会被执行。
# process.nextTick特性
- nextTick拥有自己的
TickQueue队列,且优先级高于PromiseQueue - process.nextTick会在当前宏任务执行完毕之后(调用栈清空,包括全局代码执行完毕)立即执行
- nextTick属于微任务,执行没有上限,所以如果递归调用容易造成JS阻塞。
- nextTick用于那些真正需要优先的操作,不能滥用。
← 为什么DOM操作耗性能 动态导入和懒加载 →