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

目录

  • 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);
    });
    
    // 死循环的时间 s
    function 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.mfbz.cn/a/75532.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

210、仿真-基于51单片机灭火小车超声波避障温度烟雾控制报警Proteus仿真设计(程序+Proteus仿真+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、硬件设计 二、设计功能 三、Proteus仿真图 四、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方案一&a…

java面试题(16):Mysql一致性视图是啥时候建立的

1 演示错误案例 先给大家来一个错误演示。 我们打开两个会话窗口&#xff0c;默认情况下隔离级别是可重复读&#xff0c;我们来看下&#xff1a; 首先在 A 会话中查看当前 user 表&#xff0c;查看完成后开启事务&#xff1a; 可以看到id3的数据sex是男。 接下来在 B 会话中…

全面梳理Python下的NLP 库

一、说明 Python 对自然语言处理库有丰富的支持。从文本处理、标记化文本并确定其引理开始&#xff0c;到句法分析、解析文本并分配句法角色&#xff0c;再到语义处理&#xff0c;例如识别命名实体、情感分析和文档分类&#xff0c;一切都由至少一个库提供。那么&#xff0c;你…

LeetCode ACM模式——二叉树篇(二)

刷题顺序及思路来源于代码随想录&#xff0c;网站地址&#xff1a;https://programmercarl.com 二叉树的定义及创建见&#xff1a; LeetCode ACM模式——二叉树篇&#xff08;一&#xff09;_要向着光的博客-CSDN博客 目录 102. 二叉树的层序遍历 利用队列 利用递归 10…

R语言生存分析(机器学习)(1)——GBM(梯度提升机)

GBM是一种集成学习算法&#xff0c;它结合了多个弱学习器&#xff08;通常是决策树&#xff09;来构建一个强大的预测模型。GBM使用“Boosting”的技术来训练弱学习器&#xff0c;这种技术是一个迭代的过程&#xff0c;每一轮都会关注之前轮次中预测效果较差的样本&#xff0c;…

opencv进阶01-直方图的应用及示例cv2.calcHist()

直方图是什么&#xff1f; 直方图是一种图形表示方法&#xff0c;用于显示数据中各个数值或数值范围的分布情况。它将数据划分为一系列的区间&#xff08;也称为“箱子”或“bin”&#xff09;&#xff0c;然后统计每个区间中数据出现的频次&#xff08;或频率&#xff09;。直…

Floyd(多源汇最短路)

Floyd求最短路 给定一个 n 个点 m 条边的有向图&#xff0c;图中可能存在重边和自环&#xff0c;边权可能为负数。 再给定 k 个询问&#xff0c;每个询问包含两个整数 x 和 y&#xff0c;表示查询从点 x 到点 y 的最短距离&#xff0c;如果路径不存在&#xff0c;则输出 impo…

rabbitmq的持久化

目录 队列实现持久化 如何删除队列​编辑 消息实现持久化 不公平分发 如何保障当 RabbitMQ 服务停掉以后消息生产者发送过来的消息不丢失。默认情况下 RabbitMQ 退出或由于某种原因崩溃时&#xff0c;它忽视队列和消息&#xff0c;除非告知它不要这样做。确保消息不会丢失需…

桂林小程序https证书

现在很多APP都相继推出了小程序&#xff0c;比如微信小程序、百度小程序等&#xff0c;这些小程序的功能也越来越复杂&#xff0c;不可避免的和网站一样会传输数据&#xff0c;因此小程序想要上线就要保证信息传输的安全性&#xff0c;也就是说各种类型的小程序也需要部署https…

SAP MM学习笔记24-以评估收货(评价)和非评估收货(非评价)

SAP 中 有评价入库&#xff08;评估收货&#xff09;和非评价入库&#xff08;非评估收货&#xff09;两种入库方式。 一般来说在库品目会采用评价入库&#xff0c;而消费品目&#xff0c;会采用非评价入库。 其实评价入库&#xff0c;非评价入库对外都无所谓的&#xff0c;人…

计算机竞赛 python+opencv+机器学习车牌识别

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于机器学习的车牌识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;4分工作量&#xff1a;4分创新点&#xff1a;3分 该项目较为新颖&#xff0c;适…

HTTP 协议的基本格式和 fiddler 的用法

目录 一. HTTP 协议 1. HTTP协议是什么 2. HTTP协议的基本格式 HTTP请求 首行 GET和POST方法&#xff1a; 其他方法 经典面试题&#xff1a; URL Header(请求报头)部分 空行 ​HTTP响应 状态码总结: 二、Fiddler的用法 1.Fidder的安装 2.Fidder的使用 一. HTTP 协议 1. H…

日常BUG——普通页面跳转tabbar页面报错

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;日常BUG、BUG、问题分析☀️每日 一言 &#xff1a;存在错误说明你在进步&#xff01; 一、问题描述 微信小程序页面跳转的时候出现下面的问题&#xff1a; wx.redirectTo({url: /pages/index/i…

java 使用log4j显示到界面和文件 并格式化

1.下载log4j jar包https://dlcdn.apache.org/logging/log4j/2.20.0/apache-log4j-2.20.0-bin.zip 2. 我只要到核心包 &#xff0c;看需要 sources是源码包&#xff0c;可以看到说明。在IDEA里先加入class jar后&#xff0c;再双击这个class jar包或或右键选Navigate ,Add ,…

【贪心】CF1779 D

不会1700 Problem - 1779D - Codeforces 题意&#xff1a; 思路&#xff1a; 首先手推样例&#xff0c;可以发现一些零碎的性质&#xff1a; 然后考虑如何去计算贡献 难点在于&#xff0c;当一个区间的两端是区间max时&#xff0c;怎么去计算贡献 事实上&#xff0c;只需要…

【HttpRunnerManager】搭建接口自动化测试平台操作流程

一、需要准备的知识点 1. linux: 安装 python3、nginx 安装和配置、mysql 安装和配置 2. python: django 配置、uwsgi 配置 二、我搭建的环境 1. Centos7 &#xff08;配置 rabbitmq、mysql 、Supervisord&#xff09; 2. python 3.6.8 &#xff08;配置 django、uwsgi&…

kubernetes二进制部署2之 CNI 网络组件部署

CNI 网络组件部署 一&#xff1a;K8S提供三大接口1容器运行时接口CRI2云原生网络接口CNI3云原生存储接口CSI 部署 flannelK8S 中 Pod 网络通信&#xff1a;Overlay Network&#xff1a;VXLAN&#xff1a;Flannel:Flannel udp 模式的工作原理&#xff1a;ETCD 之 Flannel 提供说…

麒麟arm架构 编译安装qt5.14.2

1、先在官网下载qt源码&#xff1a; https://download.qt.io/archive/qt/5.14/5.14.2/single/[qt源码下载地址] 2、解压编译 使用tar -xvf qt-everywhere-src-5.14.2.tar.xz 解压压缩包 cd qt-everywhere-src-5.14.2 执行 ./configure --prefix/usr/local/qt.5.14.2 make -…

计算机竞赛 python 爬虫与协同过滤的新闻推荐系统

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; python 爬虫与协同过滤的新闻推荐系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&…

Cpp学习——string模拟实现

目录 一&#xff0c;string的成员变量 二&#xff0c;string的各项功能函数 1.构造函数 2.析构函数 3.扩容函数 4.插入与删除数据的函数 5.运算符重载 6.打印显示函数 7&#xff0c;拷贝构造 8.find函数 一&#xff0c;string的成员变量 在模拟实现string之前&#xff…
最新文章