熟悉事件循环?那谈谈为什么会分为宏任务和微任务。

什么是事件循环

在了解事件循环前,需要一些有关 JS 特性的前置知识。

JS 引擎是单线程的,直白来说就是一个时间点下 JS 引擎只能去做一件事情,而 Java 这种多线程语言,可以同时做几件事情。

JS 做的任务分为同步和异步两种,所谓 “异步”,简单说就是一个任务不是连续完成的,先执行第一段,等做好了准备,再回过头执行第二段,第二段也被叫做回调;同步则是连贯完成的。

像读取文件、网络请求这种任务属于异步任务:花费时间很长,但中间的操作不需要 JS 引擎自己完成,它只用等别人准备好了,把数据给他,他再继续执行回调部分。

如果没有特殊处理,JS 引擎在执行异步任务时,应该是存在等待的,不去做任何其他事情。用一个图来展示这个过程,可以看出,在执行异步任务时有大量的空闲时间被浪费。

熟悉事件循环?那谈谈为什么会分为宏任务和微任务。

实际上这是大多数多线程语言的处理办法。但对于 JS 这种单线程语言来说,这种长时间的空闲等待是不可接受的:遇到其他紧急任务,Java 可以再开一个线程去处理,JS 却只能忙等。

所以采取了以下的”异步任务回调通知”模式:

熟悉事件循环?那谈谈为什么会分为宏任务和微任务。

在等待异步任务准备的同时,JS 引擎去执行其他同步任务,等到异步任务准备好了,再去执行回调。这种模式的优势显而易见,完成相同的任务,花费的时间大大减少,这种方式也被叫做非阻塞式。

而实现这个”通知”的,正是事件循环,把异步任务的回调部分交给事件循环,等时机合适交还给 JS 线程执行。事件循环并不是 JavaScript 首创的,它是计算机的一种运行机制。

事件循环是由一个队列组成的,异步任务的回调遵循先进先出,在 JS 引擎空闲时会一轮一轮地被取出,所以被叫做循环。

根据队列中任务的不同,分为宏任务和微任务。

宏任务和微任务

事件循环由宏任务和在执行宏任务期间产生的所有微任务组成。完成当下的宏任务后,会立刻执行所有在此期间入队的微任务。

这种设计是为了给紧急任务一个插队的机会,否则新入队的任务永远被放在队尾。区分了微任务和宏任务后,本轮循环中的微任务实际上就是在插队,这样微任务中所做的状态修改,在下一轮事件循环中也能得到同步。

常见的宏任务有:script(整体代码)/setTimout/setInterval/setImmediate(node 独有)/requestAnimationFrame(浏览器独有)/IO/UI render(浏览器独有)

常见的微任务有:process.nextTick(node 独有)/Promise.then()/Object.observe/MutationObserver

宏任务 setTimeout 的误区

setTimeout 的回调不一定在指定时间后能执行。而是在指定时间后,将回调函数放入事件循环的队列中。

如果时间到了,JS 引擎还在执行同步任务,这个回调函数需要等待;如果当前事件循环的队列里还有其他回调,需要等其他回调执行完。

另外,setTimeout 0ms 也不是立刻执行,它有一个默认最小时间,为 4ms。所以下面这段代码的输出结果不一定:

//&#xA0;node<br>setTimeout(()&#xA0;=>&#xA0;{<br>&#xA0;&#xA0;console.log('setTimeout')<br>},&#xA0;0)<br>setImmediate(()&#xA0;=>&#xA0;{<br>&#xA0;&#xA0;console.log('setImmediate')<br>})<br>

因为取出第一个宏任务之前在执行全局 Script,如果这个时间大于 4ms,这时 setTimeout 的回调函数已经放入队列,就先执行 setTimeout;如果准备时间小于 4ms,就会先执行 setImmediate。

浏览器的事件循环

浏览器的事件循环由一个宏任务队列+多个微任务队列组成。

首先,执行第一个宏任务:全局 Script 脚本。产生的的宏任务和微任务进入各自的队列中。执行完 Script 后,把当前的微任务队列清空。完成一次事件循环。

接着再取出一个宏任务,同样把在此期间产生的回调入队。再把当前的微任务队列清空。以此往复。

宏任务队列只有一个,而每一个宏任务都有一个自己的微任务队列,每轮循环都是由一个宏任务+多个微任务组成。

下面的 Demo 展示了微任务的插队过程:

Promise.resolve().then(()=>{<br>&#xA0;&#xA0;console.log('&#x7B2C;&#x4E00;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF1A;&#x5FAE;&#x4EFB;&#x52A1;1')<br>&#xA0;&#xA0;setTimeout(()=>{<br>&#xA0;&#xA0;&#xA0;&#xA0;console.log('&#x7B2C;&#x4E09;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF1A;&#x5B8F;&#x4EFB;&#x52A1;2')<br>&#xA0;&#xA0;},0)<br>})<br>setTimeout(()=>{<br>&#xA0;&#xA0;console.log('&#x7B2C;&#x4E8C;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF1A;&#x5B8F;&#x4EFB;&#x52A1;1')<br>&#xA0;&#xA0;Promise.resolve().then(()=>{<br>&#xA0;&#xA0;&#xA0;&#xA0;console.log('&#x7B2C;&#x56DB;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF1A;&#x5FAE;&#x4EFB;&#x52A1;2')<br>&#xA0;&#xA0;})<br>},0)<br>//&#xA0;&#x7B2C;&#x4E00;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF1A;&#x5FAE;&#x4EFB;&#x52A1;1<br>//&#xA0;&#x7B2C;&#x4E8C;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF1A;&#x5B8F;&#x4EFB;&#x52A1;1<br>//&#xA0;&#x7B2C;&#x56DB;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF1A;&#x5FAE;&#x4EFB;&#x52A1;2<br>//&#xA0;&#x7B2C;&#x4E09;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF1A;&#x5B8F;&#x4EFB;&#x52A1;2<br>

打印的结果不是从 1 到 4,而是先执行第四个回调函数,再执行第三个,因为它是一个微任务,比第三个回调函数有更高优先级。

Node 的事件循环

node 的事件循环比浏览器复杂很多。由 6 个宏任务队列+6 个微任务队列组成。

宏任务按照优先级从高到低依次是:

熟悉事件循环?那谈谈为什么会分为宏任务和微任务。

其执行规律是:在一个宏任务队列全部执行完毕后,去清空一次微任务队列,然后到下一个等级的宏任务队列,以此往复。

一个宏任务队列搭配一个微任务队列。六个等级的宏任务全部执行完成,才是一轮循环。

其中需要关注的是:Timers、Poll、Check 阶段,因为我们所写的代码大多属于这三个阶段。

  1. Timers:定时器 setTimeout/setInterval;
  2. Poll :获取新的 I/O 事件, 例如操作读取文件等;
  3. Check:setImmediate 回调函数在这里执行;

除此之外,node 端微任务也有优先级先后:

  1. process.nextTick;
  2. promise.then 等;

清空微任务队列时,会先执行 process.nextTick,然后才是微任务队列中的其他。下面这段代码可以佐证浏览器和 node 的差异:

console.log('Script&#x5F00;&#x59CB;')<br>setTimeout(()&#xA0;=>&#xA0;{<br>&#xA0;&#xA0;console.log('&#x7B2C;&#x4E00;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5B8F;&#x4EFB;&#x52A1;1')<br>&#xA0;&#xA0;Promise.resolve().then(function()&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;console.log('&#x7B2C;&#x56DB;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5FAE;&#x4EFB;&#x52A1;2')<br>&#xA0;&#xA0;})<br>},&#xA0;0)<br>setTimeout(()&#xA0;=>&#xA0;{<br>&#xA0;&#xA0;console.log('&#x7B2C;&#x4E8C;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5B8F;&#x4EFB;&#x52A1;2')<br>&#xA0;&#xA0;Promise.resolve().then(function()&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;console.log('&#x7B2C;&#x4E94;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5FAE;&#x4EFB;&#x52A1;3')<br>&#xA0;&#xA0;})<br>},&#xA0;0)<br>Promise.resolve().then(function()&#xA0;{<br>&#xA0;&#xA0;console.log('&#x7B2C;&#x4E09;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5FAE;&#x4EFB;&#x52A1;1')<br>})<br>console.log('Script&#x7ED3;&#x675F;')<br>
node&#x7AEF;&#xFF1A;<br>Script&#x5F00;&#x59CB;<br>Script&#x7ED3;&#x675F;<br>&#x7B2C;&#x4E09;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5FAE;&#x4EFB;&#x52A1;1<br>&#x7B2C;&#x4E00;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5B8F;&#x4EFB;&#x52A1;1<br>&#x7B2C;&#x4E8C;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5B8F;&#x4EFB;&#x52A1;2<br>&#x7B2C;&#x56DB;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5FAE;&#x4EFB;&#x52A1;2<br>&#x7B2C;&#x4E94;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5FAE;&#x4EFB;&#x52A1;3<br><br>&#x6D4F;&#x89C8;&#x5668;<br>Script&#x5F00;&#x59CB;<br>Script&#x7ED3;&#x675F;<br>&#x7B2C;&#x4E09;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5FAE;&#x4EFB;&#x52A1;1<br>&#x7B2C;&#x4E00;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5B8F;&#x4EFB;&#x52A1;1<br>&#x7B2C;&#x56DB;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5FAE;&#x4EFB;&#x52A1;2<br>&#x7B2C;&#x4E8C;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5B8F;&#x4EFB;&#x52A1;2<br>&#x7B2C;&#x4E94;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5FAE;&#x4EFB;&#x52A1;3<br>

可以看出,在 node 端要等当前等级的所有宏任务完成,才能轮到微任务: &#x7B2C;&#x56DB;&#x4E2A;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x5FAE;&#x4EFB;&#x52A1;2在两个 setTimeout 完成后才打印。

因为浏览器执行时是一个宏任务+一个微任务队列,而 node 是一整个宏任务队列+一个微任务队列。

node11.x 前后版本差异

node11.x 之前,其事件循环的规则就如上文所述:先取出完一整个宏任务队列中全部任务,然后执行一个微任务队列。

但在 11.x 之后,node 端的事件循环变得和浏览器类似:先执行一个宏任务,然后是一个微任务队列。但依然保留了宏任务队列和微任务队列的优先级。可以用下面的 Demo 佐证:

console.log('Script&#x5F00;&#x59CB;')<br>setTimeout(()&#xA0;=>&#xA0;{<br>&#xA0;&#xA0;console.log('&#x5B8F;&#x4EFB;&#x52A1;1&#xFF08;setTimeout)')<br>&#xA0;&#xA0;Promise.resolve().then(()&#xA0;=>&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;console.log('&#x5FAE;&#x4EFB;&#x52A1;promise2')<br>&#xA0;&#xA0;})<br>},&#xA0;0)<br>setImmediate(()&#xA0;=>&#xA0;{<br>&#xA0;&#xA0;console.log('&#x5B8F;&#x4EFB;&#x52A1;2')<br>})<br>setTimeout(()&#xA0;=>&#xA0;{<br>&#xA0;&#xA0;console.log('&#x5B8F;&#x4EFB;&#x52A1;3&#xFF08;setTimeout)')<br>},&#xA0;0)<br>console.log('Script&#x7ED3;&#x675F;')<br>Promise.resolve().then(()&#xA0;=>&#xA0;{<br>&#xA0;&#xA0;console.log('&#x5FAE;&#x4EFB;&#x52A1;promise1')<br>})<br>process.nextTick(()&#xA0;=>&#xA0;{<br>&#xA0;&#xA0;console.log('&#x5FAE;&#x4EFB;&#x52A1;nextTick')<br>})<br><br>

在 node11.x 之前运行:

Script&#x5F00;&#x59CB;<br>Script&#x7ED3;&#x675F;<br>&#x5FAE;&#x4EFB;&#x52A1;nextTick<br>&#x5FAE;&#x4EFB;&#x52A1;promise1<br>&#x5B8F;&#x4EFB;&#x52A1;1&#xFF08;setTimeout)<br>&#x5B8F;&#x4EFB;&#x52A1;3&#xFF08;setTimeout)<br>&#x5FAE;&#x4EFB;&#x52A1;promise2<br>&#x5B8F;&#x4EFB;&#x52A1;2&#xFF08;setImmediate)<br>

在 node11.x 之后运行:

Script&#x5F00;&#x59CB;<br>Script&#x7ED3;&#x675F;<br>&#x5FAE;&#x4EFB;&#x52A1;nextTick<br>&#x5FAE;&#x4EFB;&#x52A1;promise1<br>&#x5B8F;&#x4EFB;&#x52A1;1&#xFF08;setTimeout)<br>&#x5FAE;&#x4EFB;&#x52A1;promise2<br>&#x5B8F;&#x4EFB;&#x52A1;3&#xFF08;setTimeout)<br>&#x5B8F;&#x4EFB;&#x52A1;2&#xFF08;setImmediate)<br>

可以发现,在不同的 node 环境下:

  1. 微任务队列中 process.nextTick 都有更高优先级,即使它后进入微任务队列,也会先打印 &#x5FAE;&#x4EFB;&#x52A1;nextTick&#x5FAE;&#x4EFB;&#x52A1;promise1;
  2. 宏任务 setTimeout 比 setImmediate 优先级更高, &#x5B8F;&#x4EFB;&#x52A1;2(setImmediate)是三个宏任务中最后打印的;
  3. 在 node11.x 之前,微任务队列要等当前优先级的所有宏任务先执行完,在两个 setTimeout 之后才打印 &#x5FAE;&#x4EFB;&#x52A1;promise2;在 node11.x 之后,微任务队列只用等当前这一个宏任务先执行完。

结语

事件循环中的任务被分为宏任务和微任务,是为了给高优先级任务一个插队的机会:微任务比宏任务有更高优先级。

node 端的事件循环比浏览器更复杂,它的宏任务分为六个优先级,微任务分为两个优先级。node 端的执行规律是一个宏任务队列搭配一个微任务队列,而浏览器是一个单独的宏任务搭配一个微任务队列。但是在 node11 之后,node 和浏览器的规律趋同。

Original: https://www.cnblogs.com/cangqinglang/p/16015977.html
Author: 苍青浪
Title: 熟悉事件循环?那谈谈为什么会分为宏任务和微任务。

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/549306/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • HTTP长连接和短链接代理与网关

    长连接和短链接 代理与网关 HTTP/)服务器端网关:网关与客户端使用HTTP协议通信,使用其他协议与服务端通信 (/HTTP)客户端网关:网关与客户端使用其他协议通信,使用HTT…

    技术杂谈 2023年7月24日
    067
  • Java复习,Java知识点以及Java面试题(七)

    IO流 用来处理设备之间的数据传输 IO流的分类:按流向:输入流(读取数据);输出流(写出数据) 按数据类型:字节流: 字节输入流 读取数据 InputStream 字节输出流 写…

    技术杂谈 2023年6月21日
    056
  • [转]Jira Work with issue workflows

    A Jira workflow is a set of statuses and transitions that an issue moves through during it…

    技术杂谈 2023年5月30日
    088
  • 6.线性回归的简单评价指标

    均方误差(MSE) [\mathrm{MSE}=\frac{1}{N}\sum_{i=1}^{N}(\hat{y}^{(i)}-y^{(i)})^2 ] 均方根误差(RMSE) […

    技术杂谈 2023年7月10日
    057
  • Docker 容器虚拟化

    Docker 容器虚拟化 1、虚拟化网络 Network Namespace 是 Linux 内核提供的功能,是实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,它们有独自网络…

    技术杂谈 2023年6月21日
    0110
  • 暴力猴下载和使用教程

    暴力猴下载地址 https://wwm.lanzoub.com/iCQfP03q1q7e 使用脚本地址: https://www.tampermonkey.net/scripts….

    技术杂谈 2023年6月21日
    0207
  • 【Elasticsearch】查询并删除匹配文档之_delete_by_query

    思路:先查询确认,后精准删除 假设我想删除title是”小明今晚真的不加班”这条记录,先查看一下现有的记录: (不加班不好吗?为什么要删除呢?) tips:…

    技术杂谈 2023年7月24日
    057
  • cuda安装及百度云链接

    1、cuda11.1、11.0、10.2、10.1、10.0、8.0及其相应的cudnn版本 本篇文章是基于移动端1050ti win10安装CUDA 10.0的经验写,由于官网下…

    技术杂谈 2023年7月24日
    072
  • OpenSSL命令—pkcs7

    用途: 用于处理DER或者PEM格式的pkcs#7文件。 用法: openssl pkcs7 [-inform PEM|DER] [-outform PEM|DER] [-in f…

    技术杂谈 2023年5月31日
    074
  • 什么是精准卫星授时?什么是NTP网络时间服务器?

    什么是精准卫星授时?什么是NTP网络时间服务器? 什么是精准卫星授时?什么是NTP网络时间服务器? 京准电子科技官微——ahjzsz 时间同步技术在所有网络应用中都是至关重要的,从…

    技术杂谈 2023年6月21日
    0115
  • catch中return语句的执行时间

    catch中return语句的执行时间 先考虑以下一个普通的示例: public static int main() { System.out.println("没有参数…

    技术杂谈 2023年7月24日
    0116
  • Linux中的RCU机制

    什么是RCU? RCU(Read-Copy Update),顾名思义就是读-拷贝-修改,它是基于其原理命名的。对于被RCU保护的共享数据结构,读者不需要获得任何锁就可以访问它,但写…

    技术杂谈 2023年7月24日
    077
  • json 格式化

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/crazycode2/p/16536169.htmlAu…

    技术杂谈 2023年5月30日
    084
  • ADSL理解

    ADSL技术能够充分利用现有PSTN (Public Switched Telephone Network ,公共交换电话网),只须在线路两端加装ADSL设备即可为用户提供高宽带服…

    技术杂谈 2023年5月30日
    0108
  • AJAX基础+Axios快速入门+JSON使用+综合案例

    1、 AJAX 1.1 概述 1.1.1 作用 1.1.2 同步和异步 1.2 快速入门 1.2.1 服务端实现 1.2.2 客户端实现 1.3 案例 1.3.1 需求 1.3.2…

    技术杂谈 2023年7月24日
    095
  • 我理解的用户体验

    用户体验是什么? 用起来很爽快。这就是用户体验。 用户体验是一系列的心理学,美学,经验总结而成的一门艺术。 专用名称是UED User Experience Design(用户体验…

    技术杂谈 2023年6月21日
    0105
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球