事件循环机制

  |  

JS线程和工作线程,以及浏览器事件之间的通信机制叫做事件循环(EventLoop)

浏览器的事件循环

下图是事件循环的机制的图解,针对这个图进行解析

事件循环机制

  1. 调用堆栈(call stack)负责跟踪所有要执行的代码。

  2. 事件队列(event queue)负责将新的 function 发送到队列中进行处理,
    这也是常说的宏任务和微任务分开存放的地方

  3. 每当调用事件队列(event queue)中的异步函数时,都会将其发送到浏览器 API。

  4. JavaScript 语言本身是单线程的,而浏览器 API(浏览器事件) 充当单独的线程。

事件循环(Eventloop),它会不断检查调用堆栈是否为空。如果为空,则从事件队列中添加新的函数进入调用栈(call stack);如果不为空,则处理当前函数的调用。

Eventloop 在处理宏任务和微任务的逻辑其实还是有些不一样的,执行的情况大致如下:

  1. JavaScript 引擎首先从宏任务队列(macrotask queue)中取出第一个任务;

  2. 执行完毕后,再将微任务(microtask queue)中的所有任务取出,按照顺序分别全部执行(这里包括不仅指开始执行时队列里的微任务),如果在这一步过程中产生新的微任务,也需要执行;

  3. 然后再从宏任务队列中取下一个,执行完毕后,再次将 microtask queue 中的全部取出,循环往复,直到两个 queue 中的任务都取完。

总结起来就是:一次 Eventloop 循环会处理一个宏任务和所有这次循环中产生的微任务。

通过代码来解析这一过程

<script>
setTimeout(function test() {
console.log("setTimeout");
}, 1000);

new Promise((resolve) => {
console.log("promise 1");
resolve("promise 1");
});

new Promise((resolve) => resolve("promise 2"))
.then((res) => {
console.log(res);
return "promise 2 continue";
})
.then((res2) => {
console.log(res2);
});
new Promise((resolve) => {
setTimeout(() => resolve("promise 3"), 2000);
}).then((res) => {
console.log(res);
});
console.log("end");
</script>
  1. 当GUI线程,解析到 script标签,那么停止解析html, JS线程运行(JS线程与GUI线程是互斥的)

  2. 整一个 script 作为一个宏任务进入 callStack(执行栈),立刻执行

  3. 遇到 setTimeout(属于Browser API), 启动定时器线程开始计时

  4. 遇到 第一个 Promise,同步 resolve, 但是没有then, 没产生微任务,继续执行

  5. 遇到 第二个 Promise, 同步 resolve,then产生微任务,microTasks->[promise2], 继续执行同步代码

  6. 遇到第三个promise, 异步 setTimeout里面resolve, 定时器线程开始计时, 延后resolve,不产生微任务

  7. 本轮同步任务执行完毕,检查任务队列,先检查微任务队列,发现 microTasks->[promise2.then], 有 promise2的then回调,执行then的回调,产生新的promise,继续then,microTasks->[promise2.then.continue]

  8. 继续检查任务队列,microTasks->[promise2.then.continue], 执行完毕, 宏任务微任务队列都为空

  9. 开始下一轮EventLoop,此时,1秒过去了, setTimeout被推入宏任务队列,

  10. setTimeout的回调被执行, 没产生任何任务

  11. 开始下一轮EventLoop,此时,2秒过去了, setTimeout,的回调被执行,并 resolve(promise 3), 产生微任务,microTasks->[promise3]

  12. 检查任务队列, 执行 promise3的then回调

因此, 输出结果

promise 1
end
promise 2
promise 2 continue
setTimeout
promise 3

总结起来就是:一次 Eventloop 循环会处理一个宏任务和所有这次循环中产生的微任务。

nodejs中的事件循环机制

关于在 Node.js 服务端 Eventloop,Node.js 官网是这么描述的:

When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.

简单翻译过来就是:当 Node.js 开始启动时,会初始化一个 Eventloop,处理输入的代码脚本,这些脚本会进行 API 异步调用,process.nextTick() 方法会开始处理事件循环。下面就是 Node.js 官网提供的 Eventloop 事件循环参考流程。

NodeEventLoop

整个流程分为六个阶段,当这六个阶段执行完一次之后,才可以算得上执行了一次 Eventloop 的循环过程。我们来分别看下这六个阶段都做了哪些事情。

Timers 阶段:这个阶段执行 setTimeout 和 setInterval。
I/O callbacks 阶段:这个阶段主要执行系统级别的回调函数,比如 TCP 连接失败的回调。

idle,prepare 阶段: 只是 Node.js 内部闲置、准备,可以忽略。

poll 阶段: poll 阶段是一个重要且复杂的阶段,几乎所有 I/O 相关的回调,都在这个阶段执行(除了setTimeout、setInterval、setImmediate 以及一些因为 exception 意外关闭产生的回调),这个阶段的主要流程如下图所示。

[poll 阶段分析]
NodeEventLoop

check 阶段:执行 setImmediate() 设定的 callbacks。
close callbacks 阶段: 执行关闭请求的回调函数,比如 socket.on(‘close’, …)。

除了把 Eventloop 的宏任务细分到不同阶段外。node 还引入了一个新的任务队列 Process.nextTick()。

可以认为,Process.nextTick() 会在上述各个阶段结束时,在进入下一个阶段之前立即执行(优先级甚至超过 microtask 队列)。

Node.js 和浏览器端宏任务队列的另一个很重要的不同点是,浏览器端任务队列每轮事件循环仅出队一个回调函数接着去执行微任务队列;而 Node.js 端只要轮到执行某个宏任务队列,则会执行完队列中所有的当前任务,但是当前轮次新添加到队尾的任务则会等到下一轮次才会执行。

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 浏览器的事件循环
  2. 2. nodejs中的事件循环机制
,