JavaScript 之【事件循环】机制的深入浅出解读

news2024/10/7 5:22:23

前言:

JavaScript 作为一种单线程的开发语言,在执行的时候会有特定的风格,本章节以 JS 单线程的特点为引入,详细分析讲解了 JS 的事件循环机制

目录

    • 什么是【进程】
    • 什么是【线程】
    • 浏览器拥有哪些进程和线程
    • 【渲染主线程】的工作模式
      • 结论:
    • 异步的概念:
    • 任务执行的优先级?
      • 扩展小问答:

什么是【进程】

程序在运行的时候,需要占用一定的系统运行内存我们就可以把这块内存空间简单的理解为进程

在这里插入图片描述
每个应用在运行时至少会有一个进程空间,进程之间 相互独立,这样的设计背后考虑的是,维持一个大运行环境的稳定性,比如说,如果,QQ因为程序错误,造成崩溃了,那么由于进程空间 之间的相互独立,并不会影响到 weixin,反之如果不相互独立,有程序一但出现崩溃,就会影响到整个运行时的程序。当然,如果程序间需要进行通讯的话,则需要建立在双方“达成共识”的条件下方可互通讯。


什么是【线程】

有时候生活中,我们也常常听到过多线程的说法,大家的理解说法不一,那么对于多线程 的正确理解你知道吗?

当进程空间开辟后,就开始运行代码程序了,我们可以理解为,最后真正上手执行代码的 “人”,我们称之为线程

进程和线程的关系属于一种 包含的的关系,有了进程空间,才能诞生线程来处理具体的事务。

⼀个进程至少有⼀个线程,所以在进程开启后会自动创建⼀个线程来运行代码,该线程称之为主线程(当主线程结束时,就意味着整个程序运行结束了)

如果程序的业务能力过于复杂,为了减轻主线程的压力,主线程就会启动更多的子线程来执行代码,从而分担主线程的负载。所以⼀个进程中可以包含多个线程,这也就是,我们所听到的多线程的概念。

在这里插入图片描述


以上介绍了 有关进程线程的概念,要想更加透彻的理解 事件循环机制 的执行逻辑,我们需要先了解清楚 浏览器的进程模型概念

浏览器拥有哪些进程和线程

浏览器是⼀个多进程多线程的应用程序。现如今的浏览器为了适应多场景高复杂化的业务功能,其内部工作原理已经变得极其复杂了。为了避免相互影响,为了减少连环崩溃的几率,当启动浏览器后,它会自动启动多个进程。

在这里插入图片描述
补充: 在浏览器诸多进程中,只有浏览器进程主进程,浏览器初始化启动时,只有浏览器进程,其他线程,如:网络进程,渲染进程,都是浏览器进程去调度开辟的。

图例中的三种,为浏览器中使用频率较高,具有代表性的进程。当然浏览器的进程,远远不止于图例中的三种…

进程名进程描述
浏览器进程主要负责界面显示、用户交互、子进程管理等。浏览器进程内部会启多个线程处理不同的任务。
网络进程负责加载网络资源。网络进程内部会启动多个线程来处理不同的网络事务。
渲染进程渲染进程启动后,会开启⼀个渲染主线程,主线程负责执行 HTML、CSS、JS 代码。 渲染进程也是本章节重点讲解的内容。

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


【渲染主线程】的工作模式

在了解了浏览器的进程模型后,我们得知了渲染进程,是和我们前端开发者,最密切相关的一个进程栈,我们编写的全部代码,都需要通过渲染进程来执行,所以接下来我们就重点聊聊,在浏览器中,渲染主线程的工作方式。

渲染主线程 是浏览器中最繁忙的的线程,他需要处理的任务包括,但是不仅限于:

  • 解析HTML
  • 解析CSS
  • 执行全局 JS 代码
  • 处理图层

针对要处理的诸多任务,渲染主线程面临的最大问题,就是如何去高效的调度这些任务,并进行的合理的资源分配?

如下面临的问题:

  • 我正在执行⼀个 JS 函数,执行到⼀半的时候用户点击了按钮,我该立即去执行点击事件的处理函数吗?
  • 当前正在执行⼀个 JS 函数,执行到⼀半的时候某个计时器到达了时间,我该立即去执行它的回调吗?
  • 浏览器进程通知我“用户点击了按钮”,与此同时,某个计时器也到达了时间,我应该处理哪⼀个呢?

针对于这个问题,渲染主线程想出了设计出了一个绝妙的处理方式创建任务队列,按序排队执行

如下图例:
在这里插入图片描述

  1. 在渲染主线程启动的时候,主线程会进入到到一个无限循环的的执行模式中
  2. 在进行每一次循环时,都会检查消息队列池中当前是否有待执行的任务存在,如果有,则按顺序取出第一个去执行,执行完成后再次进入到下一次循环;如果任务池这时候没有待执行的任务时,主线程就会进入到休眠模式
  3. 其它的所有线程(包括其它进程的线程)可以随时的向消息队列池添加任务,新添加的任务会依次追加在任务池的末尾,在添加新任务的时候,如果这时主线程是休眠的状态,则会立即将其唤醒,继续执行循环拿取并执行任务。

扩展贴上一张Chrome 浏览器源码图例,如图34行开启了一个For 无限循环,对应了渲染主线程的循环执行。

贴上一张Chome浏览器源码图

结论:

这样一来,整套循环执行事务的流程,就被称之为 【事件循环】或消息循环


异步的概念:

在了解清楚了,浏览器的事件循环机制后,紧接着,就引出了 JS 的一个,新的知识点概念,叫做 异步函数

当我们的代码在执行的过程中,经常会遇到一些 无法立即执行的 任务,比如说:

  • 计时器定点完成后才需要执行的任务:setIntervalsetTimeout
  • 需要在网络通信完成后执行的任务:fetchXMLHttpRequest
  • 需要在用户交互操作触发后再去执行的任务:addEventListener

如果当渲染主线程在执行这些任务的时候,一直处于等待的状态,等待这些任务的触发时机到达,那么就会导致渲染主线程长时间处于【阻塞】的状态,从而可能会导致浏览器面临卡死崩溃的处境

同步执行图例:

在这里插入图片描述

以上图例演示了,如果 如果当渲染主线程对所有的代码都采取同步化的执行方式,那么在遇到这种非立即执行的代码时,主线程就只能按顺序依次执行,在这期间,主线程会造成严重的阻碍现象后续的事情不能及时的响应执行,页面上会呈现出卡顿延迟的表现

结论

渲染主线程承担着极其重要的工作,无论如何都不能阻塞!

对此:浏览器采用了 异步 执行的方式来解决这个问题。

异步执行图例
在这里插入图片描述

使用异步的执行方式:渲染主线程将永不阻塞

在这里插入图片描述


任务执行的优先级?

其实此处的标题,严格意义上,算是一个伪命题,因为严格意义上来说,任务是没有优先级的,所有的任务优先等级都是一样的。遵循着先进先出的规则执行=>>>>>>但是:【消息队列池】是有优先级划分的

扩展补充: 在过去,对浏览器任务队列的理解是,浏览器只有两个任务队列:一个是普通的宏任务队列,另一个是优先级高的:微任务队列。但是如今这种模式,已经被一种新的模式取代了,因为如今随着浏览器场景的越来越复杂化,两种任务队列已经满足不了现在的需求了

如今根据 W3C 最新的解释是: 点击查看W3C官方解释

  • 抛弃了过去 宏任务的说法
  • 现在每一个任务都有一个任务类型,并且同一个类型的任务,必须排在同一个消息队列,同时也提供了:不同类型的任务可以分属于不同的队列,在每一次事件循环中,浏览器可以根据实际的情况,智能调度从不同的队列中取出任务执行(这一点,随着不同的浏览器厂商,甚至是,不同时期的版本,智能调度算法都不一样,了解即可。)
  • 浏览器必须提供一个【微队列池】微队列中的任务执行优先级要高于其他所有类型队列池中的任务

附上:Chrome 浏览器源码片段图(部分):每一个字段都代表了一个任务类型。
在这里插入图片描述

在目前 Chrome 浏览器的实现中,至少包含了以下几种任务队列池:

  • 微任务队列池:用于存放需要被立即执行的任务 优先级:【最高】
  • 交互任务队列池:用于存放用户与页面交互后产生的事件处理任务 优先级:【高】
  • 延时任务队列池:用于存放计时器时间到达后待执行的回调任务 优先级:【中】
  • 网络任务队列池:用于存放网络资源请求和服务端交互处理的事务 优先级:【中】
  • …等等

对于微任务队列 的理解,可以总结为:当渲染主线程开始拿取任务开始执行的时候,倘若这时候,有很多不同类型的任务池中,都存在有排队待执行的任务,渲染主线程,会优先去,执行微任务池中的任务,直到将微任务池清空后,才会去自由调度的执行其他类型任务池中的任务。

添加任务到微任务队列的主要使用方式是:Promise,MutationObserver

Promise.resolve().then(() => {
    console.log("微任务优先级最高");
})

在这里插入图片描述

浏览器还有很多其它类型队列,由于和我们前端开发关系不大,所以不做过多阐述.


扩展小问答:

阐述一下 JS 的事件循环机制

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

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

JS 中的计时器能做到精确计时吗?为什么?

不行因为:

  1. 计算机硬件没有原子钟,无法做到精确计时。
  2. 操作系统的计时函数本身就有少量偏差,由于 JS 的计时器最终调用的是操作系统的函数,也就携带了这些偏差。
  3. 按照 W3C 的标准,浏览器实现计时器时,如果嵌套层级超过 5 层,则会带有 4 毫秒的最少时间,这样在计时时间少于 4 毫秒时又带来了偏差。
  4. 受事件循环的影响,计时器的回调函数只能在主线程空闲时运行,因此又带来了偏差

本章节 我们详细的概述分析了,基于JS为一种单线程语言的机制背后,其运行在浏览器环境中的时候,它的事件循环处理机制的逻辑,以及通过事件循环机制,也很好的理解了异步函数的概念,本章节内容不多,却是满满的干货,希望认真读完本章的小伙伴,对这一节的知识点能有新的理解。


🚵‍♂️ 博主座右铭:向阳而生,我还在路上!
——————————————————————————————
🚴博主想说:将持续性为社区输出自己的资源,同时也见证自己的进步!
——————————————————————————————
🤼‍♂️ 如果都看到这了,博主希望留下你的足迹!【📂收藏!👍点赞!✍️评论!】
——————————————————————————————

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

冒泡排序(思路+代码)

冒泡排序: 变量: flag 如果没有交换过就终止循环就是flase,终止外层循环 第二层循环:n个数比较n-1次 第一层循环: 第一趟,比较n-1次 第二趟,比较n-2次 ... ... 代码: import…

展望“智”造未来|深眸科技以AI+机器视觉,夯基产业新发展格局

近日,2023昆山元宇宙国际装备展在昆山国际会展中心落下帷幕。展会期间,各大展商携新技术、新产品、新概念纷纷亮相,为现场参展观众和嘉宾呈现如梦似幻的元宇宙盛会。 此次展会,深眸科技认识了诸多工业元宇宙领域的优质产品及企业…

Spring Boot3入门快速体验-Developing Your First Spring Boot Application

一、进入Getting Started https://spring.io/ 点击Spring Boot点击LEARN,再点击Reference Doc.: 再点击Getting Started Introducing Spring Boot, System Requirements, Servlet Containers, Installing Spring Boot, and Developing Your First Spring Boot App…

前端Vue组件之仿京东拼多多领取优惠券弹出框popup 可用于电商商品详情领券场景使用

随着技术的发展,开发的复杂度也越来越高,传统开发方式将一个系统做成了整块应用,经常出现的情况就是一个小小的改动或者一个小功能的增加可能会引起整体逻辑的修改,造成牵一发而动全身。通过组件化开发,可以有效实现单…

痞子衡嵌入式:从功耗测试角度了解i.MXRTxxx系列片内SRAM分区电源控制

大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是从功耗测试角度了解i.MXRTxxx系列片内SRAM分区电源控制。 我们知道配合 MCU 一起工作的存储器包含 ROM(Flash) 和 RAM 两类,前者主要放 RO 代码和数据,后者放 RW …

文件系统考古4:如何支持多个文件系统

Steve Kleiman 在 1986 年撰写了《Vnodes: An Architecture for Multiple File System Types in Sun UNIX》一文。这篇论文幅较短,大部分内容是数据结构的列举,以及 C 语言结构之间相互指向的图表。 Steve Kleiman是分布式文件系统领域的专家&#xff0c…

selenium 浏览器托管

selenium 浏览器托管,是启动一个浏览器,调试代码,可以运行当前调试代码,不用从启动浏览器开始从头执行 在谷歌浏览器chrome.exe 目录中打开cmd 输入下面目录,启动器浏览器 chrome.exe --remote-debugging-port9222 -…

cesium 控件 风格 白色 亮色 模式 组件变白

在要白色风格的控件的任意父级的class加上cesium-lighter即可, 如图黄色圈出来的

redis实现伪集群

主从复制配置 主从复制时只需要配置从库即可,其默认为主库模式. 打开 redis 客户端登录,使用命令 info replication 查看. 主从复制可以搭建真集群,也可以搭建伪集群 真集群就是有多台主机,每台主机 安装一个 redis.伪集群就是在一台主机上复制多份配置,修改其端口,运行多个…

ja3指纹

问题: 请求失效,带上抓包软件却可以->检测ja3浏览器指纹 方法,python 安装curl_cffi库 pip install curl_cffi 查看浏览器指纹 https://tls.browserleaks.com/json python 原生request 缺少两个指纹 使用curl_cffi之后 可以看到结果…

【计算机网络】第三章 数据链路层(可靠传输)

文章目录 第三章 数据链路层3.4 可靠传输3.4.1 可靠传输的基本概念3.4.2 可靠传输的实现机制——停止-等待协议3.4.2 可靠传输的实现机制——回退N桢协议3.4.3 可靠传输的实现机制——选择重传协议3.4.4 可靠传输的意义 第三章 数据链路层 3.4 可靠传输 可靠传输是指在数据通…

Java中可以使用哪些系统架构?怎样选择?

架构,又名软件架构,是有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计。 Java企业级的应用根据业务的复杂程度,通常使用的系统架构有应用架构、垂直应用架构、面向服务的架构(Service-Oriented Architectu…

在外远程访问NAS威联通(QNAP) - 免费内网穿透

文章目录 前言1. 威联通安装cpolar内网穿透2. 内网穿透2.1 创建隧道2.2 测试公网远程访问 3. 配置固定二级子域名3.1 保留二级子域名3.2 配置二级子域名 4. 使用固定二级子域名远程访问 转载自cpolar极点云文章:无需公网IP,在外远程访问NAS威联通QNAP【内…

1688按关键字搜索工厂数据,商品详情页数据的采集

item_search_factory-按关键字搜索工厂数据 公共参数 名称类型必须描述keyString是调用key(必须以GET方式拼接在URL中)​注册​Key获取appsecret和appkey;​secretString是调用密钥api_nameString是API接口名称(包括在请求地址中…

认知网络原理

目录 一、网络发展史 1.1、独立模式 1.2、网络互连 1.3、局域网 二、网络通信 2.1、IP地址 2.2、端口号 三、认识协议 3.1、五元组 3.2、协议分层 3.3、分层的作用 3.4、OSI七层模型 3.5、TCP/IP五层(或四层)模型 3.6、网络设备所在分…

【快应用】快应用学习之页面周期函数onBackPress无法触发?

【关键词】 onBackPress、退出提示 【问题背景】 在学习和调试快应用的过程中&#xff0c;我在子页面中的onBackPress()函数中定制了退出的一个弹框提醒&#xff0c;将它作为组件引入父页面中&#xff0c;弹框却无法触发&#xff1f; 问题代码如下&#xff1a; 子页面 <t…

集合处理常用Stream流

集合处理常用Stream流 1、Stream API介绍2、List集合常用Stream方法 stream流经常使用&#xff0c;但是遇到一些流操作时&#xff0c;会一下想不到用哪种&#xff0c;这里总结一下&#xff0c;方便自己或者读者查找 1、Stream API介绍 Stream API是Java 8引入的一项重要特性&a…

B. Nastya and Scoreboard(剪枝记忆化搜索)

Problem - 1340B - Codeforces Denis,在买了花和糖果之后&#xff08;你将在下一个任务中了解这个故事&#xff09;&#xff0c;与Nastya约会&#xff0c;向她表白要成为一对。现在&#xff0c;他们坐在咖啡馆里&#xff0c;最后... Denis请她答应在一起&#xff0c;但是... Na…

文盘 Rust -- tokio 绑定 cpu 实践

tokio 是 rust 生态中流行的异步运行时框架。在实际生产中我们如果希望 tokio 应用程序与特定的 cpu core 绑定该怎么处理呢&#xff1f;这次我们来聊聊这个话题。 首先我们先写一段简单的多任务程序。 use tokio::runtime; pub fn main() {let rt runtime::Builder::new_mu…