一、概述
开始之前,我们先来简单聊聊浏览器的组成:
- 用户界面:包括地址栏、导航按钮(前进、后退、刷新)、书签管理器等,用于与用户进行交互。
- 网络:网络组件负责处理网络请求和响应,包括发送 HTTP 请求、接收服务器响应、处理 Cookie 等。
- 数据存储:浏览器提供了多种数据存储机制,如 Cookie、Web Storage(localStorage、sessionStorage)和 IndexedDB,用于存储网页的数据和状态。
- 插件:插件是浏览器的可扩展部分,允许在浏览器中运行第三方的扩展功能。常见的插件包括 Adobe Flash Player、Java 和浏览器扩展等。
- 安全模块:览器的安全模块负责保护用户免受恶意软件和网络攻击的影响。它包括对网页来源的验证、防止跨站点脚本攻击(XSS)和跨站请求伪造(CSRF)等保护机制。
- 渲染引擎:负责解析 HTML、CSS 等页面资源,并将它们转换为用户可以交互和渲染的视觉元素。它负责处理页面的布局、样式计算、绘制和排版等操作。(如 WebKit、Blink)
- 布局引擎:布局引擎是渲染引擎的一部分,负责处理文档的结构和布局,包括解析 HTML 标记、计算元素的尺寸和位置以及处理盒模型等。它与渲染引擎一起协同工作,将页面内容渲染到用户界面上。。
- JavaScript引擎:用来 解析执行 JavaScript 代码。(如 V8 引擎、SpiderMonkey)
提示:与大多数浏览器不同的是,谷歌(Chrome)浏览器的每个标签页都分别对应一个呈现引擎实例。每个标签页都是一个独立的进程。
以下是各个主流浏览器的 JavaScript 引擎和渲染引擎:
| 浏览器 | JavaScript 引擎 | 渲染引擎 |
|---|---|---|
| Internet Explorer/Edge | Chakra | Trident/EdgeHTML |
| Mozilla Firefox | SpiderMonkey | Gecko |
| Google Chrome | V8 | Blink |
| Safari | JavaScriptCore (Nitro) | WebKit |
| Opera | V8 | Blink |
提示:请注意,这些信息基于当前的浏览器版本,可能会随着浏览器的更新和升级而发生变化。
二、浏览器架构
浏览器的架构通常采用 多进程 和 多线程 的模型,以提供更好的性能、安全性和稳定性,下面是常见的浏览器架构以及进程与线程的角色:
-
单进程架构(Single Process Architecture):
- 整个浏览器运行在单个进程中,包括用户界面、渲染引擎、JavaScript 解释器等。
- 这种架构简单,但不够稳定。如果一个页面崩溃,整个浏览器将崩溃。
-
多进程架构(Multiple Process Architecture):
- 将浏览器划分为多个独立的进程,每个进程负责不同的任务。
- 主要的进程包括:
- 浏览器进程(主进程):负责用户界面、管理其他进程以及协调各个模块的工作。
- 渲染进程:负责渲染网页内容,并与浏览器进程进行通信。每个标签页通常对应一个独立的渲染进程。
- GPU进程:负责处理图形相关的任务,如硬件加速和图形渲染。
- 插件进程:负责运行浏览器插件,如Flash。
- 网络进程:负责处理网络请求和响应。
- 下载进程:负责文件下载。
- 通过将不同任务分配给不同的进程,可以实现更好的隔离性和安全性。如果一个页面崩溃,只影响到对应的渲染进程,而不会影响整个浏览器。
-
多线程架构(Multithreading Architecture):
- 在每个进程内部,浏览器使用多线程来执行不同的任务。
- 主要的线程包括:
- 主线程:负责处理用户界面、JavaScript 执行、布局和绘制等任务。
- 渲染线程:负责将渲染进程接收到的网页内容转换为可视化的页面。
- JavaScript引擎线程:负责解析和执行 JavaScript 代码。
- 网络线程:负责处理网络请求和数据传输。
- 后台线程:负责执行一些耗时的任务,如文件下载和数据处理。
- 通过多线程,浏览器可以同时处理多个任务,提高响应性和性能。
三、渲染进程 *
浏览器的渲染进程是多线程的,那么接下来看看在 渲染进程 中都包含了哪些线程(列举一些主要常驻线程):
提示:页面的渲染,JS的执行,事件的循环,都在这个进程内进行。
- 主线程(UI线程):负责处理用户界面的渲染、解析 HTML、CSS 和 JavaScript,并处理用户输入、事件和与其他线程的通信。
- 渲染线程:负责将页面的内容渲染到屏幕上,执行渲染引擎的布局和绘制操作(HTML→DOM树,CSS→CSS规则树)。
- JavaScript 引擎线程:负责解析和执行 JavaScript 代码,实现页面的动态交互和行为。
- 事件线程:处理用户输入和事件触发,如鼠标点击、键盘输入等。
- 定时器线程:管理定时器和延迟执行的任务。
- 异步HTTP请求线程:处理异步的网络请求。
这些线程在渲染进程中协同工作,各自承担不同的任务,如页面渲染、JavaScript 执行、事件处理、定时器管理和网络请求等。它们通过线程间通信(IPC)机制进行通信和协调,以实现页面的渲染和交互。同时,这些线程之间的协作也需要遵循一些规则和机制,以保证页面的正确渲染和用户体验的流畅性。
注意 ⚠️:
由于JS引擎是单线程关系,所以这些任务队列(事件队列)中待处理的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行),所以对于定时器之类的定时有可能就不准确。
在渲染进程中,渲染线程和 JavaScript 引擎线程是互斥的,它们不能同时进行。这是因为渲染线程和 JavaScript 引擎线程需要访问和操作相同的页面内容,如 DOM 树和样式表。当渲染线程在执行布局和绘制操作时,它会阻塞 JavaScript 引擎线程的执行。同样地,当 JavaScript 引擎线程在执行 JavaScript 代码时,它会阻塞渲染线程的渲染操作。这种互斥的设计是为了避免并发访问和修改页面内容时可能产生的冲突和不一致性。
四、网络协议
网络协议是计算机网络中用于实现通信和数据传输的规则和约定。对于前端开发者来说,以下是一些常见的网络协议的简单介绍:
- HTTP(Hypertext Transfer Protocol):HTTP 是一种用于传输超文本(如 HTML)的协议,它建立在 TCP/IP 协议之上。通过 HTTP,浏览器可以向服务器发送请求并接收响应,从而获取网页内容和其他资源。
- HTTPS(HTTP Secure):HTTPS 是基于 HTTP 的安全版本,通过使用 SSL 或 TLS 加密协议来保护数据的安全性。它使用加密算法对数据进行加密传输,防止被恶意篡改或窃取。在前端开发中,使用 HTTPS 可以增加网站的安全性,保护用户的隐私数据。
- DNS(Domain Name System):DNS 是用于将域名(如 www.example.com → 192.168.0.10) 解析为IP 地址的协议。当浏览器输入一个域名时,DNS 负责将域名转换为对应的 IP 地址,以便浏览器能够发起请求并连接到服务器。
- TCP(Transmission Control Protocol):TCP 是一种可靠的传输协议,用于在网络中传输数据。它将数据分割成多个小的数据包进行传输,并通过确认、重传等机制来确保数据的可靠性和顺序性。
- IP(Internet Protocol):IP 是一种网络层协议,用于在不同的网络之间传输数据包。IP 地址是用于标识网络中设备的唯一标识符,它通过 IP 协议将数据包从源地址传输到目标地址。
- WebSocket:WebSocket 是一种在客户端和服务器之间进行全双工通信的协议,允许实时的双向数据传输。与传统的 HTTP 请求-响应模型不同,WebSocket 允许服务器主动向客户端发送消息,实现了实时性和双向通信。它在开发实时聊天、实时数据更新等应用中很有用。
这只是网络协议的简要介绍,实际上还有许多其他协议,如FTP、SMTP、POP3等,用于不同的网络通信和应用场景。了解这些网络协议的基本概念和工作原理,可以帮助前端开发者更好地理解和处理与网络相关的问题。
五、导航流程 *
在浏览器里,从输入 URL 到页面展示,这中间发生了什么?
- 用户输入:浏览器首先判断输入的内容是URL还是关键字,如果是关键字,则使用默认配置的搜索引擎来查询,如果是请求的URL,则会进入URL解析流程。
- URL解析(域名、端口号、路径、查询参数、锚点等)
- DNS解析:在解析域名的过程中,浏览器需要将域名解析为服务器的 IP 地址。浏览器首先检查本地 DNS 缓存,如果找到了对应的 IP 地址,则跳过后续步骤。否则,浏览器向本地 DNS 服务器发送 DNS 请求,该服务器会向上级 DNS 服务器递归查询,直到找到域名对应的 IP 地址。一旦找到 IP 地址,浏览器会将其缓存起来供以后使用。
- 建立TCP连接:三次握手
- 浏览器发送请求:浏览器向服务器发送 HTTP 请求。请求包括请求行(请求方法、URL 和 HTTP 版本)、请求头(Accept、User-Agent 等)和请求体(POST 请求的参数)。
- 服务器处理请求:服务器接收到请求后,根据请求的 URL 和参数,执行相应的处理逻辑。这可能涉及查询数据库、处理业务逻辑、生成动态内容等。
- 服务器响应请求:服务器生成 HTTP 响应,包括响应行(状态码、HTTP 版本)、响应头(Content-Type、Content-Length 等)和响应体(HTML、CSS、JavaScript 等)。
- 浏览器接收响应:览器接收到服务器的响应,开始按序接收响应的数据包。这些数据包通过 TCP 连接传输,并通过网络层和传输层协议进行分段、传输和重组。
- 解析HTML:浏览器接收到响应后,会根据响应头中的 Content-Type 字段确定响应的数据类型,通常为 HTML。浏览器开始解析 HTML,并构建 DOM树。
- 加载资源:在解析 HTML 过程中,如果遇到外部资源(如 CSS 文件、JavaScript 文件、图像等),浏览器会发起额外的网络请求,获取这些资源。
- 解析 CSS 和 JavaScript:浏览器解析 HTML 时,会遇到嵌入的 CSS 样式和 JavaScript 脚本。浏览器会解析 CSS 样式规则,计算元素的样式,并将其应用到对应的 DOM 元素上。JavaScript 脚本会被解析和执行,可以修改页面的结构和行为。
- 构建渲染树:浏览器根据解析得到的 DOM 树和样式规则,构建渲染树(Render Tree)。渲染树包含了所有需要显示的 DOM 元素及其样式信息。
- 布局:渲染树构建完成后,浏览器进行布局过程,计算出每个元素在屏幕上的位置和大小。布局过程也称为回流或排版。
- 绘制:布局完成后,浏览器将渲染树中的元素转换为屏幕上的实际像素,这个过程称为绘制。绘制使用图形库将元素转换为位图,并进行组合和渲染。
- 显示页面:绘制完成后,浏览器将位图发送给显示设备,显示设备将位图渲染为可见的页面。页面的内容显示在浏览器窗口中,用户可以看到并与页面进行交互。
总结起来,从输入 URL 到页面展示的过程涉及 DNS 解析、TCP 连接建立、HTTP 请求和响应、HTML 解析、CSS 和 JavaScript 解析、渲染树的构建、布局和绘制等步骤。这些步骤是浏览器完成网页加载和渲染的关键过程,使用户能够在浏览器中浏览和与网页进行交互。
六、渲染流程
按照渲染的时间顺序,流水线可分为如下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。
客户端在接收服务器传输过来的文件之后,便由浏览器的引擎开始解析文件并在屏幕中渲染出来,其步骤主要如下:
-
DOM:解析 HTML 生成 DOM 树。
-
样式:解析 CSS 样式生成CSSOM树,并将其与DOM树合并生成渲染树。
-
布局:根据渲染树进行布局(计算节点位置和大小),也称为回流或布局。
-
分层:将页面中的元素分为不同的图层,以便进行独立的绘制和合成操作。
-
绘制:根据渲染树和布局信息将图层上的元素转换为屏幕上的实际像素。
-
分块:将绘制的图形分成小块,并转换为位图数据。
-
光栅化:对不同图层的位图进行组合和混合,形成最终的页面图像。
-
合成显示:将不同图层的最终位图合成为最终的页面图像,并显示在屏幕上。
七、重排与重绘
浏览器重排(Reflow)和重绘(Repaint)是浏览器渲染过程中的两个关键概念,它们经常一起发生,但是具有不同的含义和影响。
1. 重排
重排(Reflow)是指当页面的布局或几何属性发生改变时,浏览器需要重新计算元素的几何属性(例如位置、大小)和页面的布局。这是一个相对较耗时的操作,因为它需要对整个页面的元素进行重新布局。重排会导致其他相关元素的位置和大小发生改变,可能触发更多的重排。
从上图可以看出,如果你通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。
无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。
2. 重绘
重绘(Repaint)是指当元素的样式发生改变,但不影响布局和几何属性时,浏览器会重新绘制元素的外观。重绘的过程比重排更快,因为它只需要重新绘制元素的外观,而无需改变它们的布局。
从图中可以看出,如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。
相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
3. 结论
重排和重绘可能会对页面性能产生影响,特别是在频繁触发或处理大量元素时。因此,为了提高性能,应尽量避免不必要的重排和重绘操作。
以下是一些常见触发重排和重绘的情况:
- 修改元素的位置、大小、边距、填充、边框等几何属性。
- 修改元素的文本内容或字体属性。
- 添加、移除或修改样式表(CSS)。
- 改变窗口大小。
- 操作CSS伪类(如:hover)或使用JavaScript动态修改元素样式。
- 修改表格布局或调整表格单元格大小。
- 修改DOM结构。
为了减少重排和重绘的次数,可以采取以下优化策略:
- 使用 CSS 的 transform 属性替代 top/left 等属性来实现元素的移动和动画效果,因为 transform 不会触发重排。
- 避免频繁读取布局信息,例如使用 offsetTop 和 offsetLeft 等属性。
- 将需要进行多次修改的样式操作合并为一次操作,或使用文档片段(Document Fragment)进行批量操作。
- 避免通过 JavaScript 修改样式表,尽量使用添加/删除 CSS 类名的方式操作样式。
- 使用 CSS3 的动画效果(如 transition 或 animation)代替 JavaScript 操作。
- 使用绝对定位(position: absolute 或 fixed)来减少对其他元素布局的影响。
- 避免频繁访问 offsetWidth 和 offsetHeight 等会触发重排的属性。
通过减少不必要的重排和重绘操作,可以提高页面的性能和响应速度,提升用户体验。
八、扩展
1. load 事件与 DOMContentLoaded 事件的先后
-
DOMContentLoaded事件在DOM树构建完成后触发,表示页面的DOM结构已经就绪,不需要等待外部资源的加载和渲染。 -
load事件在整个页面及其相关资源都加载完成后触发,表示页面和所有外部资源都已经加载完毕。
所以,顺序是:DOMContentLoaded → load
2. CSS加载是否会阻塞DOM树渲染?
CSS加载一般不会阻塞DOM树的渲染。浏览器会并行加载CSS文件,而DOM树的构建和渲染是与CSS加载并行进行的。内联CSS样式的解析和计算可能会在一定程度上阻塞DOM树的渲染。建议将样式表放在<head>标签中的<link>标签中引入,并尽量避免使用大量的内联样式。
3. 一个网页执行的完整过程
- 发送请求:浏览器向服务器发送请求,请求网页的URL。
- 接收响应:服务器接收到请求后,返回相应的HTML、CSS、JavaScript等资源文件。
- 构建DOM树:浏览器解析HTML文件,并构建出DOM(文档对象模型)树,表示网页的结构和内容。
- 加载CSS和JavaScript:浏览器解析HTML文件时,遇到外部CSS和JavaScript文件的引用,会发送额外的请求去加载这些文件。
- 构建渲染树:浏览器将DOM树和CSSOM(CSS对象模型)树合并成一个渲染树,用于确定每个节点在页面中的显示方式。
- 布局计算:浏览器根据渲染树的结构和样式信息,进行布局计算,确定每个元素在页面中的位置和大小。
- 绘制页面:浏览器遍历渲染树,将每个节点转换为屏幕上的实际像素,生成绘制命令并进行绘制。
- JavaScript执行:浏览器执行页面中的JavaScript代码,处理交互逻辑、数据请求、动态更新等操作。
- 事件处理:浏览器监听用户的交互事件(如点击、滚动等),并触发相应的事件处理函数。
- 网络请求:如果页面中存在异步请求(如AJAX请求),浏览器发送请求到服务器获取数据,并进行处理。
- 重绘和重排:当页面内容或样式发生变化时,浏览器进行重绘(Repaint)和重排(Reflow),更新页面的外观和布局。
- 页面加载完成:当所有资源都加载完毕,包括图片、样式表、脚本等,浏览器触发
load事件,表示页面加载完成。
4. 浏览器是如何将HTML解析成DOM树的?
- 标记化(Tokenization):浏览器读取HTML文件的字节流,并将其分解为一系列的标记(Tokens)。标记可以是开始标签、结束标签、注释、文本内容等。
- 构建节点(Node):根据标记的类型和属性,浏览器创建相应的节点(Node)。节点可以是元素节点(Element Node)、文本节点(Text Node)、注释节点(Comment Node)等。
- 构建父子关系:浏览器根据标记的嵌套关系,将节点组织成一个层次结构的树状结构。例如,开始标签和结束标签之间的内容将成为父节点的子节点。
- 解析样式和脚本:在构建DOM树的过程中,浏览器会遇到外部样式表(通过
<link>标签)和脚本(通过<script>标签)的引用。浏览器会发送额外的请求去加载这些资源,并在解析过程中处理相应的样式和脚本。 - 完成DOM树构建:当所有的标记都被处理并转换为节点后,浏览器完成了DOM树的构建。DOM树表示了HTML文档的结构和内容,每个节点代表了文档中的一个元素、文本或注释。
5. 浏览器多进程的优势?
浏览器采用多进程架构具有以下优势:
- 安全性增强:每个标签页都在独立的渲染进程中运行,各个进程之间相互隔离,一个进程的崩溃不会影响其他进程的稳定性。这样可以有效防止恶意网站通过漏洞攻击浏览器或访问其他标签页的数据。
- 提高稳定性:由于每个标签页都在独立的渲染进程中运行,如果一个页面出现问题,例如JavaScript异常或内存泄漏,只会影响该页面的渲染进程,不会导致整个浏览器崩溃。
- 提高性能:多进程架构允许浏览器充分利用多核处理器的能力,提供更好的并行处理能力和响应性能。例如,渲染进程和网络进程可以并行运行,加快页面加载和渲染速度。
- 隔离插件和扩展:插件通常在单独的进程中运行,这样即使插件崩溃或出现问题,也不会对浏览器的主进程和其他标签页造成影响。同样,浏览器扩展也可以在独立的进程中运行,确保扩展的安全性和稳定性。
- 资源管理优化:每个标签页都有自己的渲染进程,这意味着每个标签页都有独立的JavaScript堆内存和渲染资源,可以更好地管理和释放这些资源,避免内存泄漏和资源浪费。
总体而言,浏览器多进程架构提供了更高的安全性、稳定性和性能。它通过进程隔离和并行处理来提供更好的用户体验,并增强了浏览器对恶意代码和安全威胁的防御能力。
6. GUI渲染线程与JS引擎线程的关系?
GUI渲染线程和JavaScript引擎线程是浏览器中两个重要的线程,它们之间有一定的关系和协作。下面是它们之间的关系:
- GUI渲染线程(也称为渲染主线程):GUI渲染线程负责处理页面的布局、绘制和渲染,将页面内容显示在用户界面上。它会构建和维护渲染树,执行样式计算、布局和绘制等操作。GUI渲染线程通常是单线程的,意味着它在同一时间只能处理一个任务。
- JavaScript引擎线程:JavaScript引擎线程负责解析和执行JavaScript代码。它会处理页面中的JavaScript脚本,执行函数、处理事件、修改DOM等。JavaScript引擎线程通常也是单线程的,意味着它在同一时间只能执行一个任务。
这两个线程之间存在一定的协作关系:
- 阻塞问题:JavaScript引擎线程执行JavaScript代码时,如果遇到耗时的任务(如循环、大量计算等),会阻塞GUI渲染线程的执行,导致页面出现卡顿或无响应的情况。因此,长时间运行的JavaScript代码会影响页面的渲染和用户交互体验。
- 异步任务:为了避免阻塞GUI渲染线程,JavaScript引擎线程采用异步任务的机制。通过使用异步函数、回调函数、Promise等方式,可以将耗时的操作放到后台执行,不阻塞GUI渲染线程的执行。当异步任务完成后,会通过事件循环机制将结果返回给JavaScript引擎线程。
- 交互操作:当用户进行交互操作(如点击、滚动等),会触发相应的事件。这些事件会由GUI渲染线程监听并处理,然后通知JavaScript引擎线程执行相应的事件处理函数。在事件处理函数中,可以修改DOM、执行JavaScript代码等操作。
需要注意的是,虽然GUI渲染线程和JavaScript引擎线程在执行上是分离的,但它们之间的协作和通信是通过浏览器内部的机制实现的,例如事件循环机制、消息队列等。这样的设计可以确保页面的渲染和交互操作的顺利进行,提供流畅的用户体验。