阅读 651

JS事件循环

JS是单线程语言,始终只有一个线程执行JS代码(Web Worker没有改变JS单线程的本质),对于异步操作,是通过事件循环(Event Loop)来实现的。

任务

每个异步操作都是一个任务,任务分为宏任务微任务

  • 宏任务:script、setTimeout、setInterval、setImmediate、I/O、UI交互事件
  • 微任务:Promise(Promise的then和catch)、process.nextTick、Object.observe(已废弃)

其中, setImmediateprocess.nextTick是Node独有的,UI交互事件肯定是浏览器独有的。

澄清两个误区

  • 事件循环和JS代码是在一个线程中运行的
  • 经过测试,不管是浏览器端还是Node端,每个宏任务执行完后,都会去清除微任务

特别是第二点,可能Node做了更新(当前时间是2019-01-13,Node v11.4.0),如下面代码,不管浏览器还是Node,都会输出:1 2 3 4

setTimeout(function () {
  new Promise(function (resolve) {
    console.log(1);
    resolve();
  }).then(function () {
    console.log(2);
  });
});

setTimeout(function () {
  new Promise(function (resolve) {
    console.log(3);
    resolve();
  }).then(function () {
    console.log(4);
  });
});

// delay(); // 为了消除定时器最小延迟带来的影响,可以执行一下耗时操作,确保上面两个setTimeout都触发了

function delay() {
  for (let i = 0; i < 1000000000; i++) {}
}
复制代码

定时器的最小延迟问题见这里

差异

浏览器的事件循环是HTML5定义的规范,Node的事件循环是libuv库实现的,但截至目前(2019-01-13),两者的表现基本一致,Node的事件循环比浏览器要复杂,下面看Node的事件循环。

Node的事件循环

Node的事件循环,每一轮有六个阶段

1. Timer阶段

执行可用的setTimeout/setInterval回调

2. I/O callbacks阶段

执行可用的I/O回调,除了setTimeoutsetIntervalsetImmediateClose callbacks都属于这个阶段。

3. idle, prepare阶段

这两个阶段主要是Node做一些内部操作,忽略。

4. Poll阶段

这是轮询阶段,如果没有可用的setTimeout/setInterval/setImmediate/Close callbacks回调,会一直停留在这个阶段,等待I/O回调。

这个阶段不会一直停留,达到一定条件后,会到下一个阶段。

5. Check阶段

专门用于执行setImmediate

6. Close callbacks阶段

关闭请求在这里执行(socket.on('close', ()=>{})),这个阶段就像一个清理阶段。

如果事件循环还活着,就继续下一轮循环。

最后

欢迎关注我的微博@狂刀二