浏览器事件循环原理-技术鸭论坛-前端交流-技术鸭(jishuya.cn)

浏览器事件循环原理

进程

进程是计算机系统中正在运行的程序的实例。它是操作系统对一个正在运行的程序的抽象表示,负责管理程序的执行和资源分配。

通常它包括堆栈,例如

临时数据,如函数参数、返回地址和局部变量和数据段,其中数据段包括全局变量。

进程还可能包括堆,这是在进程运行时动态分配的内存。在 JavaScript 中,堆和栈的内存分配是通过不同方式进行的:

  1. 堆内存分配:

    • JavaScript 中的对象、数组和函数等复杂数据类型都存储在堆内存中;
    • 使用 new 关键字或对象字面量语法创建对象时,会在堆内存中动态分配相应的内存空间;
    • 堆内存的释放由垃圾回收机制自动处理,当一个对象不再被引用时,垃圾回收机制会自动回收其占用的堆内存,释放资源;
  2. 栈内存分配:

    • JavaScript 中的基本数据类型,如数字、布尔值和字符串以及函数的局部变量保存在栈内存中;
    • 栈内存的分配是静态的,编译器在编译阶段就确定了变量的内存空间大小;
    • 当函数被调用时,会在栈内存中创建一个称为栈帧 stack frame 的数据结构,用于存储函数的参数、局部变量、返回地址等信息;
    • 当函数执行完毕或从函数中返回时,对应的栈帧会被销毁,栈内存中的数据也随之释放;

在操作系统中,每个进程都有自己的地址空间、状态和控制信息。进程可以独立运行,与其他进程离开来,互不干扰。它们可以同时进行,并通过进程间通信机制进行交互

线程

线程是进程中的一个执行路径,是进程的组成部分。在同一个进程中的多个线程共享进程的资源,如内存空间和文件句柄等。不同线程之间可以并发执行,各自堵路地完成特定的任务

Chrome: 多进程架构浏览器

现代 Web 浏览器提供标签是浏览,它运行 Web 浏览器的一个实例,同时打开多个网站,而每个标签代表一个网站。要在不同网站之间切换,用户只需点击响应的标签

这种方法的一个问题是: 如果任何标签的 Web 应用程序崩溃,那么整个进程,包括所有其他标签所显示的网站也会崩溃。

GoogleChrome Web 浏览器通过多进程架构的设计解决这以问题。Chrome 具有多种不同类型的进程,这里主要讲讲其中三个:

  • 浏览器进程: 主要负责界面显示、用户交互、子进程管理,同时提供存储等功能;
  • 网络进程: 网络进程负责处理浏览器内发起的所有网络请求,例如加载网页、资源文件,如图片、CSS JavaScriptXMLHttpRequest 和 Fetch API 等请求;
  • 渲染进程: 主要负责渲染网页的逻辑。主要处理 HTML、Javascript、图像等等。一般情况下,对应于新标签的每个网站都会创建一个新的渲染进程。因此,可能会有多个渲染进程同时活跃;

9d37543266192148

例如我只打开了两个标签页,浏览器就会开辟两个两个不同的进程,两者之间相互独立,一个崩掉了不会另外一个.

渲染主线程是如何工作的

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

  • 解析 HTML;
  • 解析 CSS;
  • 计算样式;
  • 布局;
  • 处理图层;
  • 每秒把⻚面画 60 次;
  • 执行全局 JavaScript 代码;
  • 执行事件处理函数;
  • 执行计时器的回调函数;

等等

当浏览器的网络线程收到 HTML 文档后,会产生一个渲染任务,并将其传递给渲染主线程的消息队列,在事件循环的作用下,渲染主线程取出消息队列中的渲染任务,并开启渲染流程。整个过程分为多个阶段,分别是: 解析 HTML、样式计算、布局、分层、绘制、分块、光栅化、画,每个阶段都有明确的输入输出,上一个阶段的输出会成为下一个阶段的输入。

首先解析的是 HTML 文档,这里我们忽略 CSS 的文件,如果主线程解析到 script 位置,会停止解析 HTML,转而等待 JavaScript 文件下载好,主线程将 JavaScript 代码解析执行完成后,才能继续解析 HTML。这是因为 JavaScript 代码的执行过程中可能会修改当前的 DOM 树,所以 DOM 树的生成必须暂停。这就是 JavaScript 会阻塞 HTML 解析的根本原因。

70e4b4679e192809

要处理这么多的事情,浏览器给渲染进程采用了多线程,它主要包含了以下线程:

  • 主线程: 主线程负责解析 HTMLCSSJavaScript,构建 DOM 树、CSSOM 树和渲染树,并进行页面布局和绘制。它还处理用户交互,执行 JavaScript 代码以及其他页面渲染相关的任务;
  • 合成线程: 合成线程负责将渲染树转换为图层,并执行图层的合成操作;
  • 网络线程: 网络线程负责处理网络请求和数据传输。当浏览器需要加载网页、图片或其他资源时,网络线程负责发送请求并接收响应数据;
  • 定时器线程: 定时器线程负责管理定时器事件,包括 setTimeoutsetInterval 等。它用于在指定的时间间隔内触发预定的任务;
  • 事件处理线程: 当用户进行交互操作,如点击按钮、滚动页面、输入文本等,需要触发相应的事件处理函数;

由于主线程和合成线程是并行执行的,这就可能导致这两个线程之间存在数据交互的问题。例如,当主线程和合成线程都需要访问相同的共享资源时,就需要进行同步,以避免竞态条件等问题。

这里就设计到消息队列的作用: 主线程和合成线程之间通过消息队列进行通信。主线程将渲染任务和图层数据等信息封装成消息,并将消息放入消息队列中。合成线程从消息队列中获取消息,并执行相应的图层合成操作。

浏览器中出现消息队列是为了处理异步任务和事件。在浏览器当中,有许多人任务是在后台执行或者将来某个事件触发时才执行的,例如:

  • 异步操作: 比如通过 AJAX 请求从服务器获取数据、读取本地文件等,这些操作需要等待网络请求或者文件读取完成后再处理响应的数据;
  • 定时器: 通过 setTimeoutsetInterval 设置的定时器任务,需要在指定的时间间隔后执行;
  • 事件处理: 当用户进行交互操作,如点击按钮、滚动页面、输入文本等,需要触发相应的事件处理函数;

26a27aad64192926

如上图所示,当渲染主线程正在执行一个 JavaScript 函数,执行到一半的时候用户点击了按钮或者碰到了一个定时器,也就是 setTimeout。因为在我们的渲染进程里面是有定时器线程的,定时器线程监听到有这个定时器操作。那么该线程会将 setTimeout 里面的事件处理函数 (setTimeout 的第一个回调函数) 作为一个任务拿去排队。

因为消息队列采用的是队列的数据结构,当渲染主线程将所有任务情况之后,然后从消息队列中拿去最旧的那个任务,假设消息队列之前没有任务的情况下,就拿出 setTimeout 这个事件处理函数。如果在该函数当中又遇到了类似的事件处理函数或者定时器,按照前面的步骤。依此循环,直到所有任务执行完成。

整个过程,就被称之为事件循环。

什么是异步

JavaScript 是一门单线程的编程语言.意味着在一个特定的时间点,只能有一个代码块在执行。当执行一个同步任务时,如果任务需要很长时间才能完成,如网络请求、文件读取等,整个程序会被阻塞,导致用户界面无响应,甚至造成卡顿的问题。这种情况在 Web 应用中尤其常见,因为 JavaScript 经常与网络请求、DOM 操作等耗时任务打交道。

常见的异步操作包括:

  • 网络请求: 发送 HTTP 请求并等待服务器响应时,通常使用异步方式,以允许程序继续执行其他操作;
  • 定时器: 设置定时器,在一段时间后执行某个任务,也是异步操作的一种;
  • 事件处理: 为 DOM 元素注册事件监听器是一种常见的异步任务。当特定事件触发时,相应的事件处理函数将被异步调用,例如 addEventListener

渲染主线程负责处理网页的构建、布局、绘制和用户交互等任务,而异步编程使得我们可以在主线程执行同步代码的同时,处理耗时的异步操作,例如网络请求、文件读写等,以提高程序的性能和用户体验。在 JavaScript 中,通过事件循环机制,异步编程实现了一种非阻塞的执行方式,使得浏览器能够高效地处理各种任务,同时保持用户界面的响应性。

setTimeout(() => {
  console.log(111);
}, 3000);

console.log(222);

在这段代码当中,如果让渲染主线程去等待这个定时器任务执行完再去执行下一个任务,就会导致主线程长期处于阻塞的状态,从而导致浏览器页面长期见不到效果,可能要砸电脑了。

等到整个计时结束,再执行 console.log(222) 的代码,这种模式就叫作同步。整个时候消息队列还有很多任务在等待,可能还存在一些渲染页面的任务,有可能直接导致整个页面卡死。

所以为了这个问题,浏览器采用了异步的方式来解决这个问题,因为渲染主线程承担着及其重要的工作,无论如何都不能阻塞。

计时任务会放到计时线程,当计时结束之后,会把该回调函数放入到消息队列末尾.等待主线程依次执行。

请登录后发表评论

    请登录后查看回复内容