浏览器 - 事件循环机制详解

目录

  • 1,浏览器进程模型
    • 进程
    • 线程
    • 浏览器的进程和线程
      • 1,浏览器进程
      • 2,网络进程
      • 3,渲染进程
  • 2,渲染主线程
    • 事件循环
    • 异步
    • 同步 JS 为什么会阻塞渲染
    • 任务优先级
  • 3,常见面试题
    • 1,如何理解 js 的异步
    • 2,讲一下 js 的事件循环
    • 3,js 能做到精准计时吗

1,浏览器进程模型

进程

简单理解:程序的运行是需要内存空间的。这块内存空间就可以理解为进程

另外每个应用程序至少会有一个进程。进程之间相互独立,通信需要双方同意。

线程

有了进程(内存)后,就能运行程序了。而具体运行程序代码的是进程的小弟——线程

  • 1个进程至少有1个线程(因为需要线程来运行程序代码)。所以在进程开启后,会默认创建1个线程——主线程
  • 如果程序需要同时执行多个代码,会由主线程来开启更多的线程来执行。所以进程和线程之间是一对多的关系。
    在这里插入图片描述

浏览器的进程和线程

现在浏览器是一个多进程多线程的程序。为了避免相互影响和减少连环崩溃的概率,当启动浏览器程序后,它会自动启动多个进程。

在这里插入图片描述

其中主要的进程有:

1,浏览器进程

主要负责页面显示,用户交互(比如点击滑动),子进程管理。它会启动多个线程来处理不同的任务。

2,网络进程

负责加载网络资源,它也会启动多个线程来处理各种网络任务。

3,渲染进程

它默认创建的主线程,被称为渲染主线程。负责执行 HTML CSS JS 代码。

默认情况下,浏览器会为每个标签页创建1个新的渲染进程,以保证不同标签页之间互不影响。

2,渲染主线程

事件循环

渲染主线程是浏览器中最忙的线程,因为需要处理的东西有很多,包括但不限于:

  1. 解析 HTML
  2. 解析 CSS
  3. 计算样式
  4. 布局
  5. 处理图层
  6. 画页面 60次/s
  7. 执行全局 js 代码
  8. 执行绑定事件的处理函数
  9. 执行计时器中的回调函数

问题来了,如何调度这么多的任务?举例来说

  • 正在执行 js 函数的过程中,用户点击了某个按钮,是否应该立即执行点击事件的处理函数?
  • 正在执行 js 函数的过程中,计时器时间到了,是否应该立即执行对应的回调函数?
  • 计时器时间到了,同时又点击了按钮,应该先执行哪个?

渲染主线程通过排队的方式来处理这个问题。
在这里插入图片描述
1,渲染主线程会进入无限循环。

2,每次循环会检查消息队列中是否存在任务 --> 有则取出第1个任务执行–> 执行完进入下次循环。没有任务则进入休眠状态。

3,其他所有线程(包括其他进程的子线程),可以随时向消息队列的末尾添加任务。
添加任务时,如果渲染主线程是休眠状态,则会唤醒让它继续循环取出任务执行。

注意,消息队列不止一个(常见有微队列,延时队列,交互队列)

以上整个过程被称为——事件循环

异步

在执行代码时,会遇到一些无法立即处理的任务。

  • 计时器结束时需要执行的任务。—— setTimeout setInterval
  • 网络通信完成后需要执行的任务。—— XHR Fetch
  • 用于操作后需要执行的任务。—— EventListener

如果渲染主线程一直等待这些任务的时间点到来,那就会一直处于阻塞的状态,表现为浏览器卡死。

浏览器选择用异步来解决这个问题。这样渲染主线程永不阻塞

在这里插入图片描述

同步 JS 为什么会阻塞渲染

因为 js 运行在渲染主线程中,所以是单线程的语言。而渲染主线程有许多的工作,包括渲染页面执行全局 js

举例:

<body><h1>下雪天的夏风</h1><button>更改内容</button><script>const h1 = document.querySelector("h1");const btn = document.querySelector("button");btn.addEventListener("click", function () {h1.textContent = "求关注";delay(2000);});// 死循环的时间 sfunction delay(duration) {const start = Date.now();while (Date.now() - start < duration) {}}</script>
</body>

点击按钮效果:
在这里插入图片描述
解释:首先,页面内容需要重新绘制才会更新。

1,当全局 js 执行到绑定事件时,会通知交互线程来处理。当某个时间用户点击后,会将 fn 包装为任务发送给消息队列。

2,当 fn 执行时,第1条语句会创建1个绘制任务,接着死循环 2s 后,此时 fn 才执行完成。渲染主线程再去消息队列中看有没有新的任务。

这就是同步 js 会阻塞渲染的原因
在这里插入图片描述

任务优先级

任务没有优先级,在消息队列中先进先出。但消息队列有优先级

1,每个任务都有一个任务类型,同一类型的任务必须在一个队列;不同类型的任务可以分属不同的队列。
一次事件循环中,浏览器可以根据实际情况选择从不同的队列中取出任务执行。

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

现代浏览器的复杂度升高了许多,W3C 不再使用宏队列的说法。
不过我们可以简单的将,除微队列之外的其他队列 “统一看做宏队列”。

3,在目前的 Chrome 浏览器中,至少包含了以下队列(其他的和前端开发关系不大,省略)

  • 微队列,存放需要最快执行的任务,优先级最高。
  • 交互队列,存放用户操作后产生的事件处理任务,优先级高。
  • 延时队列,存放计时器时间结束后的回调任务,优先级中。

交互队列和延时队列,优先级举例如下:

可以理解为:用户的交互行为需要立即响应,而计时器已经等了一段时间了再等一小会也无所谓。

<body><button id="btn1">初始化</button><button id="btn2">执行交互队列</button><script>/* 1,addDelay()执行完成时(2s后),计时器的回调函数 A,会被添加到延时队列。2,接着 addInteraction() 执行完成时(2s后),会将按钮点击事件的回调函数 B,会被添加到交互队列。3,此时,点击【执行交互队列】,交互队列中的 B 会先于延时队列中的 A 执行。*/const init = document.querySelector("#btn1");const btnInteraction = document.querySelector("#btn2");init.addEventListener("click", function () {addDelay();addInteraction();// “添加交互队列” 被打印后,需要立即点击【执行交互队列】console.log("-----");});function addDelay() {console.log("添加延时队列");setTimeout(() => {console.log("执行延时队列");}, 0);// 等 2s 为了保证 addDelay 执行完后,计时器的回调函数添加到延时队列delay(2000);}function addInteraction() {console.log("添加交互队列");btnInteraction.addEventListener("click", function () {console.log("执行交互队列");});// 等 2s 为了保证 addInteraction 执行完后,按钮点击事件的回调函数添加到交互队列delay(2000);}function delay(duration) {const start = Date.now();while (Date.now() - start < duration) {}}</script>
</body>

效果:

在这里插入图片描述

另外,添加任务到微队列,主要使用 Promise 和 MutationObserver

// 立即把 fn 添加到微队列。
Promise.resolve().then(fn)

几个测试题:

1,结果: 2,1

setTimeout(() => {console.log(1);
}, 0);
console.log(2);

2,结果:5,4,3,1,2

function a() {console.log(1);Promise.resolve().then(function () {console.log(2);});
}setTimeout(() => {console.log(3);Promise.resolve().then(a);
}, 0);Promise.resolve().then(function () {console.log(4);
});console.log(5);

3,结果:5,1,2,3

function a() {console.log(1);Promise.resolve().then(function () {console.log(2);});
}setTimeout(() => {console.log(3);
}, 0);Promise.resolve().then(a);console.log(5);

3,常见面试题

搞清楚了上面的内容后,下面的问题也就比较简单了。

单线程是异步产生的原因。
事件循环是异步的实现方式。

1,如何理解 js 的异步

因为 js 运行在渲染主线程中,所以是单线程的语言。而渲染主线程有许多的工作,包括渲染页面执行全局 js

如果使用同步的方式,大概率会造成渲染主线程阻塞,进而导致消息队列中的很多其他任务无法及时执行。表现为页面无法及时更新,给用户造成浏览器卡死的现象。

所以浏览器采用异步的方式来避免这个问题。具体做法:

当有某些任务产生后,计时器,网络通信,事件监听等,渲染主线程会交给其他对应线程去处理,自身继续执行后面的代码。当其他线程处理完成后,会将实现传递的回调函数包装为任务,发送到消息队列末尾,等待渲染主线程调度执行。

在这种异步模式下,浏览器不会被阻塞,最大限度的保证了单线程的流畅运行。

2,讲一下 js 的事件循环

事件循环又叫做消息循环,是浏览器渲染主线程的工作方式。

在 Chrome 的源码中,会开启一个不会结束的 for 循环for(;;){},每次循环从消息队列中取出第1个任务执行,其他线程将产生的任务加入到消息队列末尾即可。

过去把消息队列简单的分为微队列宏队列。现在已经无法满足现代浏览器的复杂环境了。

根据 W3C 的解释,每个任务都有类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。而不同队列的优先级也不一样。

一次事件循环中,由浏览器来决定调取那个队里中的任务。同时必须有一个优先级最高的微队列,必须优先调度执行。

3,js 能做到精准计时吗

不能。

1,受事件循环的影响,计时器的回调函数所在的延时队列优先级较低,这样即便倒计时结束,也得等某些队列的任务执行完成,所以会带来偏差。

2,W3C 的标准,浏览器实现的倒计时,如果嵌套超过5层,默认会有 4ms 的最少时间,这也是可能的偏差。

3,操作系统的计时函数本身就有少量误差,js 计时器调用的就是它,所以也会有偏差。

4,计算机硬件没有原子钟,无法做到精准计时。

原子钟(英语:Atomic clock)是一种时钟,它以原子共振频率标准来计算及保持时间的准确。原子钟是世界上已知最准确的时间测量和频率标准,也是国际时间和频率转换的基准,用来控制电视广播和全球定位系统卫星的讯号。

以上。


参考:渡一教育。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/91981.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何使用CSS实现一个响应式网格布局?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用CSS实现响应式网格布局⭐ 设置基本的HTML结构⭐ 创建基本的CSS样式⭐ 添加媒体查询以实现响应式效果⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端…

【wiki】电竞助手掉落提醒 EsportsHelper「Webhook」「钉钉」「饭碗警告」「企业微信」「Discord」

介绍 本项目链接 Github电竞助手链接 github上项目电竞助手(EsportsHelper)的掉落提醒配置教程,当有掉宝的时候会发送你信息提示. 至于这个脚本是怎么使用的简单说一下,就是通过自动观看英雄联盟直播 从而获取奖励(仅限直营服),有兴趣的可以去github上看readme,非常详细,支持…

ARM--day2(cpsr、spsr、数据搬移指令、移位操作指令、位运算操作指令、算数运算指令、比较指令、跳转指令)

.text .global _gcd _gcd:mov r0,#9mov r1,#15b loop loop:cmp r0,r1beq stopsubhi r0,r1bhi loopsubcc r1,r0bcc loopstop:b stop.end用for循环实现1~100之间和5050 .text .global _gcd _gcd:mov r0,#0x0mov r1,#0x1mov r2,#0x64b loop loop:cmp r1,r2bhi stopadd r0,r0,r1ad…

epoll数据结构

目录 1.大量的fd 集合。选择什么数据结构&#xff1f;2、Epoll 数据结构Epitem 的定义Eventpoll 的定义 1.大量的fd 集合。选择什么数据结构&#xff1f; 查找频率很高的数据结构 1.红黑树 2.哈希&#xff08;扩容缩容&#xff09; 3. b/btree &#xff08;降低树的高度&#…

【学会动态规划】环形子数组的最大和(20)

目录 动态规划怎么学&#xff1f; 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后&#xff1a; 动态规划怎么学&#xff1f; 学习一个算法没有捷径&#xff0c;更何况是学习动态规划&#xff0c; 跟我…

UI设计师个人工作总结范文精选

UI设计师个人工作总结范文(一) 在忙忙碌碌中&#xff0c;2019年又将过去了&#xff0c;在这一年当中&#xff0c;设计部无论是在运作模式、设计产值、还是人员结构&#xff0c;各方面的变化都比较大。 设计部的运作模式是从7月底开始进行调整的&#xff0c;以独立承包制的运营方…

pgsql checkpoint机制(1)

检查点触发时机 检查点间隔时间由checkpoint_timeout设置pg_xlog中wall段文件总大小超过参数max_WAL_size的值postgresql服务器在smart或fast模式下关闭手动checkpoint 为什么需要检查点&#xff1f; 定期保持修改过的数据块作为实例恢复时起始位置&#xff08;问题&#xf…

网盘与相册服务PDS

引言&#xff1a;作为一名开发者&#xff0c;我将通过对PDS&#xff08;Personal/Enterprise Drive System&#xff09;的体验使用&#xff0c;分享一下本人对以下方面的使用体验。 1. 开发个人/企业网盘 功能与应用 PDS作为一种网盘服务中间件产品&#xff0c;为开发者提供了…

【调整奇数偶数顺序】

调整奇数偶数顺序 1.题目 输入一个整数数组&#xff0c;实现一个函数&#xff0c; 来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分&#xff0c; 所有偶数位于数组的后半部分。 2.题目分析 这道题首先用到的方法是冒泡排序的思想&#xff0c;首先通过冒泡排序…

C#__匿名方法和Lambda表达式

class Program{static void Main(string[] args){// 匿名方法&#xff1a;方法没有名字Func<int, int, int> plus delegate (int a, int b){return a b;};// 这里相当于直接把要引用的方法直接写在后面// 优点&#xff1a;减少了要编写的代码&#xff0c;减少代码的复杂…

大数据大屏的分析

今天又进行了大屏的训练&#xff0c;就是很多的报表头是最难的&#xff0c;因为确定了头&#xff0c;就确定了大屏的风格了。 今天的还是有点丑但是也是学习了。报班报班~~~~

论文略读:城市道路场景下车辆编队运动规划与控制算法研究

1. 一些观点&#xff1a; &#xff08;1&#xff09;我曾经认为不能复现的论文都是垃圾。我现在看到能够量产的论文之后发现&#xff0c;论文的复现实属难得&#xff0c;即使给你代码&#xff0c;反复钻研&#xff0c;一个月之久才敢说略微看懂&#xff0c;所以论文的复现实在是…

xarray 简易体会与实现

1 基础原理 xarray1主要由 xarray 结点组成&#xff0c;xarray 结点主要由槽位&#xff08;即指针&#xff09;、父节点指针等组成。xarray 根据整型索引组织 xarray 结点实现对目标值的高效存、查、删操作。 此文以 存查删等流程对应源码2具体实例 —— xarray 结点槽位数 …

E8—Aurora 64/66B ip实现GTX与GTY的40G通信2023-08-12

1. 场景 要在贴有K7系列FPGA芯片的板子和贴有KU系列FPGA芯片的板子之间通过光模块光纤QSFP实现40G的高速通信。可以选择的方式有多种&#xff0c;但本质的方案就一种&#xff0c;即实现4路GTX与GTY之间的通信。可以选择8B/10B编码通过GT IP核实现&#xff0c;而不能通过Aurora…

深入探索:解读创意的力量——idea的下载、初步使用

目录 ​编辑 1.IDEA的简介 2.IDEA的下载 2.1下载路径https://www.jetbrains.com/zh-cn/idea/download/?sectionwindows​编辑​ 2.2下载的步骤 3 idea的初步使用 3.1新建一个简单的Java项目 3.1.1首先需要创建一个新的工程 3.1.2创建一个新的项目&#xff08;模块&am…

Spring项目整合过滤链模式~实战应用

代码下载 设计模式代码全部在gitee上,下载链接: https://gitee.com/xiaozheng2019/desgin_mode.git 日常写代码遇到的囧 1.新建一个类,不知道该放哪个包下 2.方法名称叫A,干得却是A+B+C几件事情,随时隐藏着惊喜 3.想复用一个方法,但是里面嵌套了多余的逻辑,只能自己拆出来…

WPS-0DAY-20230809的分析和利用复现

WPS-0DAY-20230809的分析和初步复现 一、漏洞学习1、本地复现环境过程 2、代码解析1.htmlexp.py 3、通过修改shellcode拿shell曲折的学习msf生成sc 二、疑点1、问题2、我的测试测试方法测试结果 一、漏洞学习 强调&#xff1a;以下内容仅供学习和测试&#xff0c;一切行为均在…

使用wxPython和PyMuPDF提取PDF页面指定页数的内容的应用程序

在本篇博客中&#xff0c;我们将探讨如何使用wxPython和PyMuPDF库创建一个简单的Bokeh应用程序&#xff0c;用于选择PDF文件并提取指定页面的内容&#xff0c;并将提取的内容显示在文本框中。 C:\pythoncode\new\pdfgetcontent.py 准备工作 首先&#xff0c;确保你已经安装了…

【Python机器学习】实验10 支持向量机

文章目录 支持向量机实例1 线性可分的支持向量机1.1 数据读取1.2 准备训练数据1.3 实例化线性支持向量机1.4 可视化分析 实例2 核支持向量机2.1 读取数据集2.2 定义高斯核函数2.3 创建非线性的支持向量机2.4 可视化样本类别 实例3 如何选择最优的C和gamma3.1 读取数据3.2 利用数…

服务器遭受攻击之后的常见思路

哈喽大家好&#xff0c;我是咸鱼 不知道大家有没有看过这么一部电影&#xff1a; 这部电影讲述了男主是一个电脑极客&#xff0c;在计算机方面有着不可思议的天赋&#xff0c;男主所在的黑客组织凭借着超高的黑客技术去入侵各种国家机构的系统&#xff0c;并引起了德国秘密警察…