由于Js最初是面向交互的语言,多线程会导致高昂数据同步成本,所以被设计成单线程异步模式,而异步部分是通过事件循环机制实现,原理是主线程将耗时的异步任务挂起,然后继续执行主线程其他任务,等任务结果返回后,添加到事件队列,然后 even loop 是一个单独的线程,不断轮询事件队列,当主线程调用栈清空了,将队列中的任务加入主线程的调用栈中执行。但会出现事件队列阻塞导致执行时间不精准的问题. 提出个微任务和宏任务的概念
微任务执行时机是在调用栈清空, 且控制权尚未交给用户代理(比如:浏览器)维护的事件队列之前执行, 最常建的微任务 有 Promise, Generator, await/async, queueMicrotask, MutationObserver, 在 node 端还有 nextTick.
宏任务就是指消息队列中的等待被主线程执行的事件, 常见, setTimeout、setInterval、 setImmediate, XMLHttpRequest, I/O 等等众多宿主提供的 API
而还需要注意的是不同的用户代理实现事件循环的方式是不同的, 刚才讲的是浏览器端, 而在 node 端, 的事件循环机制是在 libuv 包中实现的, libuv引擎中的事件循环分为 6 个阶段
- timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调
- I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调
- idle, prepare 阶段:仅node内部使用
- poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
- check 阶段:执行 setImmediate() 的回调
- close callbacks 阶段:执行 socket 的 close 事件回调
由于node 端读写/调用其他服务的场景更多, 所以每个事件执行时长的不确定性更高, 所以被设计成会在每个阶段检查是否有要执行的微任务
参考