JS的事件循环(Event Loop)以及宏任务和微任务

1. 浏览器的进程

首先我们要知道,浏览器是多进程多线程的应用程序

在这里插入图片描述

一个浏览器的主要进程包括 三个,分别是以下三个

  1. 浏览器进程
  2. 网络进程
  3. 渲染进程

其实还有其他的进程,比如:GPU 进程插件进程

具体的职责

1.1 浏览器进程

职责

  • 管理浏览器窗口和标签页
  • 处理用户界面(如地址栏、书签、前进/后退按钮
  • 协调其他进程的工作。

注意:每个浏览器实例,只有一个主进程

1.2 网络进程

就是处理所有的网络请求的,比如 HTTPHTTPSWebSocket

1.3 渲染进程

渲染进程启动后,会开启一个渲染主线程,主要负责执行html、css、js代码。
一般来说,每个标签页,会有一个独立渲染进程,但目前有的为了浏览器性能优化,可能会存在同一个站点共用一个渲染进程

2. 渲染主线程

在这里插入图片描述

渲染主线要处理好多东西

  1. 解析 HTML:将 HTML 文档解析为 DOM(Document Object Model)树
  2. 解析 CSS:将 CSS 文件解析为 CSSOM(CSS Object Model)
  3. 构建渲染树(Render Tree):将 DOM 树和 CSSOM 树结合,生成 渲染树
  4. 布局(Layout):计算渲染树中每个节点的几何信息(如位置、大小)
  5. 执行JS脚本
  6. 处理事件
  7. 处理网络请求
  8. 等等

3 事件循环

  1. 在最开始的时候,渲染主线程会进入一个无限循环
  2. 每一次循环会检查消息队列中是否有任务存在。如果有,就取出第一个任务执行,执行完一个后进入下一次循环;如果没有,则进入休眠状态。
  3. 其他所有线程(包括其他进程的线程)可以随时向消息队列添加任务。新任务会加到消息队列的末尾。在添加新任务时,如果主线程是休眠状态,则会将其唤醒以继续循环拿取任务

这样一来,就可以让每个任务有条不紊的、持续的进行下去了。

整个过程,被称之为事件循环(消息循环)

如何解释 事件循环呢?
完整点的解释就是i下面这样
事件循环又叫做消息循环,是浏览器渲染主线程的工作方式
在 Chrome 的源码中,它开启一个不会结束的 for 循环,每次循环从消息队列中取出第一个任务执行,而其他线程只需要在合适的时候将任务加入到队列末尾即可。
过去把消息队列简单分为宏队列和微队列,这种说法目前已无法满足复杂的浏览器环境,取而代之的是一种更加灵活多变的处理方式。
根据 W3C官方的解释,每个任务有不同的类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪一个队列的任务。但浏览器必须有一个微队列,微队列的任务一定具有最高的优先级,必须优先调度执行。

4. 异步

浏览器在执行我们写的代码的时候,难免会遇到一些无法立即处理的代码,比如

  • 计时完成后需要执行的任务 —— setTimeoutsetInterval
  • 网络通信完成后需要执行的任务 – XHRFetch
  • 用户操作后需要执行的任务 – addEventListener

如果让渲染主线程一直等着这些任务执行或者到达执行时机,那渲染主线程就是长期处于 阻塞了,就是 浏览器上就卡死了。所以JS 引用了异步这个概念来处理这些问题

JS 引入异步编程的原因是为了解决单线程模型的局限性,避免阻塞主线程,提高程序的性能用户体验。通过事件循环回调函数Promise 和 Async/Await 等机制,JavaScript 能够高效地处理异步任务

这样的话,就可以让渲染主线程不阻塞(如若写个死循环还是会阻塞)

如何解释 JS 的 异步呢
js是一门单线程语言,这是因为它运行在浏览器的渲染主线程里面,而渲染主线程只有一个,而渲染主线程承担着诸多的工作,渲染界面、执行JS 都在其中运行
如果使用同步的方式,就极有可能导致主线程产生阻塞,从而导致消息队列中的很多其他任务无法得到执行,这样一来 一方面会导致繁忙的主线程白白的消耗时间,另一方面导致界面无法及时更新,给用户造成卡死现象
所以浏览器采用异步的方式来避免。具体做法是当某些任务发生时,比如计时器、网络、事件监听,主线程将任务交给其他线程去处理,自身立即结束任务的执行,转而执行后续代码。当其他线程完成时,将事先传递的回调函数包装成任务,加入到消息队列的末尾排队等待主线程调度执行
这种模式下浏览器用不阻塞,从而最下限度的保证了单线程的流畅运行

4.1 阻塞的例子

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <h2>测试按钮</h2>
  <script>
    let heEL = document.querySelector('h2')
    heEL.addEventListener('click', function () {
      heEL.innerText = '你点击了'

      var strat = Date.now()
      while (Date.now() - strat < 3000) { }
    })

    //  结果是,会先等待三秒,然后再更改文字
    // 在 JavaScript 中,事件处理程序的更改会在下一次重新渲染之前应用。因为浏览器是单线程的,所以当 JavaScript 执行时,它会阻塞其他任务(例如重新渲染页面)。
    // 在你的代码中,当点击事件触发时,你先将 < h2 > 元素的文本更改为 '你点击了'。然后,你进入一个 while 循环,并且该循环会一直运行 3 秒钟。由于 JavaScript 是单线程的,这个循环会阻塞其他任务(例如页面的重新渲染),因此页面上的更改将无法立即显示。

    // 当循环结束后,JavaScript 引擎才能够继续执行其他任务,其中包括重新渲染页面。在重新渲染之后,你的代码才会生效,将 < h2 > 元素的文本更改为 '你点击了'。
  </script>
</body>

</html>

可以看上面的GIF 就会发现,点击按钮后,是等待了三秒后,界面上才渲染为你点击了,按照正常来说应该是点击后立即更新。

DOM 更新是同步了,执行到heEL.innerText = '你点击了' 会立即更新 DOM 树。
但是界面的渲染是要等主线程空闲时才会进行,这个循环会阻塞三秒,所以界面不会立即显示更新后的内容

在这里插入图片描述

从这个图也可以看到,这个 绘制,这一步操作,是在点击事件执行完以后

heEL.innerText = ‘你点击了’; 的具体流程

  1. DOM 更新:JavaScript 更新 < h2 > 元素的文本内容。
  2. 样式计算:浏览器重新计算 < h2> 元素的样式。
  3. 布局:如果文本内容的变化影响了布局,浏览器会重新计算布局。
  4. 绘制:浏览器将更新后的内容绘制到屏幕上。
  5. 合成:如果有多个图层,浏览器会将它们合并为最终的图像。
  6. 显示:最终图像显示在屏幕上。

5. 消息队列的优先级

在这里插入图片描述

任务没有优先级,在消息队列中先进先出 ,但消息队列是有优先级的
根据 W3C 的最新解释:

  • 每个任务都有一个任务类型,同一个类型的任务必须在一个队列,不同类型的任务可以分属于不同的队列。

  • 在一次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行。

  • 浏览器必须准备好一个微队列,微队列中的任务优先所有其他任务执行

    https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint

随着浏览器的复杂度急剧提升,W3C 不再使用宏队列的说法

在目前 chrome 的实现中,至少包含了下面的队列:

  • 延时队列:用于存放计时器到达后的回调任务,优先级「
  • 交互队列:用于存放用户操作后产生的事件处理任务,优先级「
  • 微队列:用户存放需要最快执行的任务,优先级「最高

6. 实际练习

6.1 第一个

  1. 渲染主线程开始执行代码,首先会输出 1
  2. 发现定时器,放入延时队列
  3. 输出 3
  4. 发现 Promise (new Promise 是同步代码),发现第一个then 放入微队列,然后还有一个 then 也放入进去,等待渲染主线程先执行完毕
  5. 输出 6
  6. 这个时候主线程执行完毕,就要看 微队列,有没有东西,如若发现有就执行,这个时候就会先输出 5,然后输出 6
    在这里插入图片描述
    在这里插入图片描述

7. 其他

之前,事件循环,不是队列的概念,是宏任务微任务的概念

在这里插入图片描述

先执行宏任务,执行完成后,看看是否有微任务,如若有,则拿出来执行,如若都执行完成后,在开始新的宏任务,具体的宏任务和微任务有这样

在这里插入图片描述

参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值