浏览器 - 事件循环机制详解
目录
- 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,渲染主线程
事件循环
渲染主线程是浏览器中最忙的线程,因为需要处理的东西有很多,包括但不限于:
- 解析 HTML
- 解析 CSS
- 计算样式
- 布局
- 处理图层
- 画页面 60次/s
- 执行全局 js 代码
- 执行绑定事件的处理函数
- 执行计时器中的回调函数
…
问题来了,如何调度这么多的任务?举例来说
- 正在执行 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)是一种时钟,它以原子共振频率标准来计算及保持时间的准确。原子钟是世界上已知最准确的时间测量和频率标准,也是国际时间和频率转换的基准,用来控制电视广播和全球定位系统卫星的讯号。
以上。
参考:渡一教育。
相关文章:

浏览器 - 事件循环机制详解
目录 1,浏览器进程模型进程线程浏览器的进程和线程1,浏览器进程2,网络进程3,渲染进程 2,渲染主线程事件循环异步同步 JS 为什么会阻塞渲染任务优先级 3,常见面试题1,如何理解 js 的异步2&#x…...
析构函数中不应该抛出异常(摘录)
析构函数中抛出异常时概括性总结 从语法上面讲,析构函数抛出异常是可以的,C并没有禁止析构函数引发异常,但是C不推荐这一做法,从析构函数中抛出异常是及其危险的。 如果析构函数抛出异常,则异常点之后的程序不会执行&a…...

Windows定时任务计划无法显示任务程序界面的问题解决
笔者这两天写了一个python脚本程序,用来自动从公司的主数据系统获取数据,并按格式编制成excel。脚本程序编写一切顺利,运行结果很是完美,笔者很是舒心。但在最后一步,用上班的电脑每天早上定时运行它时,出了…...

【Azure API 管理】APIM如何实现对部分固定IP进行访问次数限制呢?如60秒10次请求
问题描述 使用Azure API Management, 想对一些固定的IP地址进行访问次数的限制,如被限制的IP地址一分钟可以访问10次,而不被限制的IP地址则可以无限访问? ChatGPT 解答 最近ChatGPT爆火,所以也把这个问题让ChatGPT来解答&#x…...

Python学习笔记_进阶篇(二)_django知识(一)
本章简介: Django 简介Django 基本配置Django urlDjango viewDjango 模板语言Django Form Django 简介 Django是一个开放源代码的Web应用框架,由Python写成。采用了MVC的软件设计模式,即模型M,视图V和控制器C。它最初是被开发来…...

【hive】hive中row_number() rank() dense_rank()的用法
hive中row_number() rank() dense_rank()的用法 一、函数说明 主要是配合over()窗口函数来使用的,通过over(partition by order by )来反映统计值的记录。 rank() over()是跳跃排序,有两个第二名时接下来就是第四名(同样是在各个分组内)dense_rank() …...

【云原生】【k8s】Kubernetes+EFK构建日志分析安装部署
目录 EFK安装部署 一、环境准备(所有主机) 1、主机初始化配置 2、配置主机名并绑定hosts,不同主机名称不同 3、主机配置初始化 4、部署docker环境 二、部署kubernetes集群 1、组件介绍 2、配置阿里云yum源 3、安装kubelet kubeadm …...

计算实数数组中所有元素的绝对值 numpy.fabs()
【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 计算实数数组中所有元素的绝对值 numpy.fabs() [太阳]选择题 请问关于以下代码表述错误的是? iimport numpy as np a np.array([-1,-3]) b np.array([-1,34j]) print("【显…...
深入浅出Pytorch函数——torch.nn.init.orthogonal_
分类目录:《深入浅出Pytorch函数》总目录 相关文章: 深入浅出Pytorch函数——torch.nn.init.calculate_gain 深入浅出Pytorch函数——torch.nn.init.uniform_ 深入浅出Pytorch函数——torch.nn.init.normal_ 深入浅出Pytorch函数——torch.nn.init.c…...
ORACLE中UNION、UNION ALL、MINUS、INTERSECT学习
1、UNION和UNION ALL的使用与区别 如果我们需要将两个select语句的结果作为一个整体显示出来,我们就需要用到union或者union all关键字。union的作用是将多个结果合并在一起显示出来。 union和union all的区别是union会自动压缩多个结果集合中的重复结果ÿ…...

【k8s、云原生】基于metrics-server弹性伸缩
第四阶段 时 间:2023年8月18日 参加人:全班人员 内 容: 基于metrics-server弹性伸缩 目录 一、Kubernetes部署方式 (一)minikube (二)二进制包 (三)Kubeadm 二…...

回归预测 | MATLAB实现WOA-SVM鲸鱼算法优化支持向量机多输入单输出回归预测(多指标,多图)
回归预测 | MATLAB实现WOA-SVM鲸鱼算法优化支持向量机多输入单输出回归预测(多指标,多图) 目录 回归预测 | MATLAB实现WOA-SVM鲸鱼算法优化支持向量机多输入单输出回归预测(多指标,多图)效果一览基本介绍程…...
VSCode快捷键
CtrlShiftP,F1:显示命令面板 CtrlP:快速打开 CtrlShiftN:新窗口/实例 CtrlShiftW:关闭窗口/实例 CtrlX:剪切行 CtrlC:复制行 ALT↑/↓:上下移动 ShiftAlt↓/↑:向…...
贪心算法求数组中能组成三角形的最大周长
题目:三角形的最大周长 给定由一些正数(代表长度)组成的数组arr,返回由其中三个长度组成的、面积不为零的三角形的最大周长。 如果不能形成任何面积不为零的三角形,返回0。 分析: 对数组排序,再从大到小选择三个数,再…...

VMWare Workstation 17 Pro 网络设置 桥接模式 网络地址转换(NAT)模式 仅主机模式
文章目录 网络模式配网要求CentOSDHCP虚拟网络桥接模式默认配置测试手动配置测试 网络地址转发模式 (NAT)还原配置虚拟网络配置默认配置测试手动配置测试 仅主机模式 网络模式 桥接模式: 主机与虚拟机对等, 虚拟机注册到主机所在的局域网, 会占用该网络的IP该局域网内的所有机…...

拒绝摆烂!C语言练习打卡第四天
🔥博客主页:小王又困了 📚系列专栏:每日一练 🌟人之为学,不日近则日退 ❤️感谢大家点赞👍收藏⭐评论✍️ 目录 一、选择题 📝1.第一题 📝2.第二题 Ὅ…...

KubeSphere 社区双周报 | Java functions framework 支持 SkyWalking | 2023.8.4-8.17
KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书、新增的讲师证书以及两周内提交过 commit 的贡献者,并对近期重要的 PR 进行解析,同时还包含了线上/线下活动和布道推广等一系列社区动态。 本次双周报涵盖时间为:2023.08.04-2023.…...

【学习笔记之java】使用RestTemplate调用第三方接口
1.首先需要导入依赖 <!-- RestTemplate使用导入的依赖--><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.13</version></dependency>2.跟启动类同级创建…...

数据集成革新:去中心化微服务集群的无限潜能
在当今数据密集型的业务环境下,传统的集中式架构已经难以满足高可用性和高并发性的要求。而去中心化微服务集群则通过分散式的架构,将系统划分为多个小型的、独立部署的微服务单元,每个微服务负责特定的业务功能,实现了系统的高度…...
后端返回可下载的xlsx文件,但是前端接收下载后为乱码
我的接收数据进行处理的代码为: download_signal_list() {return new Promise((resolve, reject) > {get_download_signal({project_id: this.projectId,version_id: this.versionId}).then(response > {const url window.URL.createObjectURL(new Blob([res…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...