一、浏览器从输入 url 到页面渲染的整个流程
请求响应阶段
1. DNS 解析:
-
浏览器首先检查自身的 DNS 缓存,如果缓存中有该 URL 对应的 IP 地址,就直接使用;如果没有,浏览器会向本地 DNS 服务器发送查询请求。
-
本地 DNS 服务器也会先检查自己的缓存,如果没有找到对应的记录,它会向根 DNS 服务器发起查询。根 DNS 服务器会返回负责该顶级域名(如.com、.org 等)的 DNS 服务器地址。
-
本地 DNS 服务器再向顶级域名 DNS 服务器查询,顶级域名 DNS 服务器会返回该域名对应的权威 DNS 服务器地址。
-
最后,本地 DNS 服务器向权威 DNS 服务器查询,权威 DNS 服务器返回该 URL 对应的 IP 地址,本地 DNS 服务器将结果缓存起来,并返回给浏览器。
2. 建立 TCP 连接
浏览器获取到 IP 地址后,会与服务器建立 TCP 连接。这个过程通过三次握手来完成:
-
客户端发送SYN包到服务器:浏览器向服务器发送一个 SYN 包(同步序列号),表示请求建立连接。
-
服务器发送SYN+ACK包到客户端:服务器收到 SYN 包后,返回一个 SYN-ACK 包(同步确认),表示同意建立连接。
-
客户端发送ACK包到服务器:浏览器收到 SYN-ACK 包后,再发送一个 ACK 包(确认),至此,TCP 连接建立成功。
3. 发送 HTTP 请求
- TCP 连接建立后,浏览器会向服务器发送一个 HTTP 请求报文,请求报文中包含请求方法(如 GET、POST 等)、请求的 URL、协议版本(如 HTTP/1.1、HTTP/2 等)以及一些请求头信息(如 User-Agent、Accept 等)。
4. 服务器处理请求并返回响应
-
服务器接收到浏览器的请求后,会对请求进行处理。根据请求的内容,服务器可能会查询数据库、读取文件等操作。
-
处理完成后,服务器会返回一个 HTTP 响应报文,响应报文中包含协议版本、状态码(如 200 表示成功,404 表示未找到等)、状态描述以及响应头信息(如 Content-Type、Content-Length 等),还包含响应的主体内容(如 HTML 页面、图片、数据等)。
5. 关闭 TCP 连接
数据传输完成后,浏览器和服务器之间会通过四次挥手来关闭 TCP 连接:
-
客户端发送FIN包到服务器:浏览器发送一个 FIN 包(结束标志),表示请求关闭连接。
-
服务器发送ACK包到客户端:服务器收到 FIN 包后,返回一个 ACK 包确认收到关闭请求。
-
服务器发送FIN包到客户端:服务器处理完剩余的工作后,发送一个 FIN 包给浏览器,表示服务器也准备关闭连接。
-
客户端发送ACK包到服务器:浏览器收到服务器的 FIN 包后,返回一个 ACK 包确认,至此,TCP 连接关闭。
解析渲染阶段
1. 解析(Parsing)
-
HTML 解析:
- 词法分析:将 HTML 标签、属性等转换为标记(Tokens)。
- 语法分析:根据标记构建DOM树(Document Object Model),表示页面的层级结构。
- 容错处理:自动修复缺失的标签(如未闭合的
<p>)。
-
CSS 解析:解析 HTML 过程中,如遇 CSS 链接,浏览器会发起新的请求获取 CSS 文件,并对 CSS 进行解析和计算样式,将CSS规则转换为CSSOM树(CSS Object Model)。
-
JavaScript 处理:通过JavaScript引擎(如V8)解析和执行脚本,当遇到 JavaScript 脚本时,如果脚本是同步的(没有
async或defer属性),浏览器会暂停 HTML 解析,先执行 JavaScript 代码,因为 JavaScript 代码可能会修改 DOM 结构或样式。如果脚本是异步的(有async或defer属性),则会在不阻塞 HTML 解析的情况下下载并执行脚本。
2. 构建渲染树(Render Tree)
- 合并DOM与CSSOM:将DOM树和CSSOM树结合,生成渲染树(Render Tree),仅包含需要显示的节点(如忽略
display: none的元素)。 - 计算样式:为每个节点确定最终的CSS样式(继承、层叠、优先级)。
3. 布局(Layout/Reflow)
- 计算几何信息:根据渲染树计算每个节点的精确位置和大小(如宽度、边距等)。
- 视口(Viewport) :依据设备屏幕尺寸调整布局,响应式设计在此阶段生效。
4. 绘制(Painting)
- 生成绘制指令:将布局结果转换为像素数据,包括文本、颜色、边框等。
- 分层(Layers) :某些元素(如
position: fixed)会被提升到单独的图层,优化渲染性能。 - 光栅化(Rasterization) :将图层转换为位图,通常由GPU加速。
5. 合成(Compositing)
- 合并图层:浏览器将各图层按正确顺序合成,最终显示到屏幕上。
- 优化:仅重绘变化的图层(如动画),减少性能开销。
二、常见的浏览器存储方式及区别
常见的浏览器存储方式有 Cookie、LocalStorage、SessionStorage 和 IndexedDB,以下为你详细介绍它们的区别:
1. 存储大小
- Cookie:存储容量有限,一般单个
Cookie的大小限制在 4KB 左右。如果需要存储较多的数据,使用Cookie会受到很大限制。 - LocalStorage:存储容量相对较大,通常为 5MB 左右,不同浏览器可能会有细微差异。这使得它可以存储一些相对较大的数据,如用户的偏好设置、离线数据等。
- SessionStorage:存储容量和
LocalStorage类似,一般也是 5MB 左右。不过,它的生命周期与当前会话相关。 - IndexedDB:存储容量非常大,理论上只受限于设备的硬盘空间。它适合存储大量的结构化数据,如数据库记录、图片等。
2. 数据有效期
- Cookie:可以设置过期时间。如果设置了过期时间,
Cookie会在指定时间后自动删除;如果没有设置过期时间,Cookie会在浏览器关闭时删除。 - LocalStorage:数据会一直存储在浏览器中,除非手动清除。这意味着即使关闭浏览器、重新打开浏览器或者重启计算机,数据依然存在。
- SessionStorage:数据仅在当前会话期间有效。当用户关闭当前窗口或标签页时,
SessionStorage中的数据会被清除。 - IndexedDB:数据会一直存储在浏览器中,除非手动删除数据库或者用户清除浏览器缓存。
3. 数据传输
- Cookie:每次 HTTP 请求都会将
Cookie发送到服务器端,这会增加请求的数据量,影响性能。同时,服务器也可以修改Cookie并将其返回给客户端。 - LocalStorage:数据仅存储在客户端,不会随 HTTP 请求发送到服务器端,因此不会增加请求的负担。
- SessionStorage:同样,数据只存储在客户端,不会在请求中传输。
- IndexedDB:数据存储在客户端,不参与 HTTP 请求的数据传输。
4. 访问权限
- Cookie:可以通过 JavaScript 的
document.cookie属性进行读写操作,也可以在服务器端进行设置和读取。同时,Cookie可以设置路径和域名,以限制其访问范围。 - LocalStorage:只能通过 JavaScript 的
localStorage对象进行访问,不同域名下的LocalStorage是相互隔离的,即一个域名下的页面无法访问另一个域名下的LocalStorage。 - SessionStorage:通过 JavaScript 的
sessionStorage对象访问,同样遵循同源策略,不同域名和不同窗口或标签页之间的SessionStorage是相互独立的。 - IndexedDB:通过 JavaScript 的 IndexedDB API 进行操作,也是基于同源策略,不同源的页面无法访问彼此的 IndexedDB 数据库。
5. 应用场景
- Cookie:常用于存储用户的登录信息、用户偏好设置等,以便在不同页面或请求之间保持状态。同时,服务器可以根据
Cookie来识别用户。 - LocalStorage:适合存储一些不经常变化的数据,如用户的主题设置、离线数据缓存等。由于其数据持久性,用户下次访问页面时可以快速恢复之前的状态。
- SessionStorage:适用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页后数据不需要保留的场景,如购物车信息、表单填写进度等。
- IndexedDB:用于存储大量的结构化数据,如 Web 应用中的数据库、本地缓存的图片或文件等。它提供了类似于传统数据库的功能,如事务处理、索引查询等。 综上所述,不同的浏览器存储方式各有优缺点,开发者可以根据具体的需求选择合适的存储方式。
3、强缓存和协商缓存
强缓存和协商缓存是浏览器缓存机制中的两种重要策略,用于提高网页的加载性能和用户体验。
强缓存
强缓存是指浏览器在请求资源时,先检查本地缓存中是否有该资源,并且该资源是否在有效期内。如果在有效期内,浏览器直接从本地缓存中读取资源,而不会向服务器发送请求。
- 相关的 HTTP 头字段
Expires:这是一个 HTTP 1.0 时期的字段,它的值是一个绝对的时间点,即资源的过期时间。当浏览器请求资源时,如果本地缓存的资源还未超过这个时间点,就会直接使用缓存中的资源。例如:Expires: Thu, 01 Dec 2022 12:00:00 GMT。Cache-Control:这是 HTTP 1.1 中定义的字段,它比Expires更灵活和强大。常见的值有:max-age:表示资源在本地缓存中可以有效的秒数。例如Cache-Control: max-age=3600,表示资源在 1 小时内都是有效的,在这期间浏览器会直接从缓存中读取。public:表示该资源可以被任何缓存(包括代理服务器和浏览器)缓存。private:表示该资源只能被终端用户的浏览器缓存,不能被代理服务器缓存。no-cache:表示资源需要先与服务器验证是否仍然有效,然后才能使用缓存,这实际上不是强缓存的控制方式,而是协商缓存的一种触发方式。no-store:表示禁止缓存该资源,每次请求都必须从服务器获取。
- 工作流程
- 浏览器第一次请求资源时,服务器会在响应头中返回
Expires或Cache-Control字段,告知浏览器该资源的缓存策略。 - 浏览器将资源存储在本地缓存中,并记录相关的缓存信息。
- 当浏览器再次请求该资源时,会检查本地缓存中的资源是否在有效期内(根据
Expires或Cache-Control中的max-age判断)。如果在有效期内,浏览器直接从本地缓存中读取资源,返回 200 OK 状态码,并在响应头中添加from cache标识。
协商缓存
协商缓存是指浏览器在请求资源时,先检查本地缓存中是否有该资源,如果有,则向服务器发送请求,验证缓存中的资源是否仍然有效。如果有效,服务器会返回一个 304 Not Modified 状态码,告知浏览器可以使用本地缓存中的资源;如果无效,服务器会返回新的资源。
- 相关的 HTTP 头字段
Last-Modified和If-Modified-Since:Last-Modified是服务器在响应头中返回的字段,表示资源的最后修改时间。浏览器在下次请求该资源时,会在请求头中带上If-Modified-Since字段,其值为上次响应中Last-Modified的值。服务器收到请求后,会比较If-Modified-Since和资源的实际最后修改时间,如果相等,说明资源没有变化,返回 304 Not Modified;如果不相等,说明资源已更新,返回新的资源。ETag和If-None-Match:ETag是服务器为资源生成的一个唯一标识符,通常是基于资源的内容生成的哈希值。浏览器在下次请求该资源时,会在请求头中带上If-None-Match字段,其值为上次响应中ETag的值。服务器收到请求后,会比较If-None-Match和当前资源的ETag,如果相等,返回 304 Not Modified;如果不相等,返回新的资源。
- 工作流程
- 浏览器第一次请求资源时,服务器在响应头中返回
Last-Modified或ETag字段。 - 浏览器将资源存储在本地缓存中,并记录相关的缓存信息。
- 当浏览器再次请求该资源时,会在请求头中带上
If-Modified-Since或If-None-Match字段(根据上次响应头中的字段决定)。 - 服务器收到请求后,根据请求头中的字段与当前资源的状态进行比较。如果资源没有变化,返回 304 Not Modified 状态码,浏览器使用本地缓存中的资源;如果资源已更新,返回新的资源。
区别总结
- 是否发送请求:强缓存期间,浏览器不会向服务器发送请求,直接使用本地缓存;协商缓存时,浏览器会向服务器发送请求,验证资源的有效性。
- 状态码:强缓存命中时,返回 200 OK 状态码,并带有
from cache标识;协商缓存命中时,返回 304 Not Modified 状态码。 - 缓存控制字段:强缓存主要依赖
Expires和Cache-Control字段;协商缓存主要依赖Last-Modified和If-Modified-Since或ETag和If-None-Match字段。
三、Websocket 及其工作原理
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,传统的 HTTP 协议里,通信模式是半双工且请求-响应式的,WebSocket 则打破了这种限制,它允许服务器和客户端在连接建立后,任意时刻都能相互发送数据,实现真正的实时通信。
特点
- 全双工通信:连接建立后,客户端和服务器能够同时独立地向对方发送数据,极大地提升了通信效率,适用于实时性要求高的场景。
- 低开销:与 HTTP 相比,WebSocket 连接建立后的数据包头部开销小,通信时无需频繁携带大量的 HTTP 头部信息,从而减少了数据传输量,降低了带宽消耗。
- 持久连接:一旦建立连接,除非手动关闭,否则会一直保持,避免了频繁建立和断开连接带来的性能损耗。
- 跨平台兼容性:主流的浏览器(如 Chrome、Firefox、Safari 等)和服务器端技术都对 WebSocket 提供了支持,具有良好的跨平台特性。
工作原理
1. 握手阶段
客户端发起一个 HTTP 请求,请求头中包含特定的字段,如 Upgrade: websocket 和 Connection: Upgrade,表明客户端希望将连接从 HTTP 升级为 WebSocket。服务器收到请求后,如果支持 WebSocket,会返回一个状态码为 101 的响应,表示同意升级连接。此时,连接从 HTTP 升级为 WebSocket,后续的数据传输将遵循 WebSocket 协议。
2. 数据传输阶段
连接建立后,客户端和服务器可以随时通过这个连接发送和接收数据。数据以帧的形式进行传输,WebSocket 协议定义了不同类型的帧,如文本帧、二进制帧等。
3. 关闭连接阶段
当通信结束时,任何一方都可以发送关闭帧来关闭连接,另一方收到关闭帧后,会发送一个确认关闭帧,然后双方关闭 TCP 连接。
四、HTTP 各个版本的区别
-
HTTP/1.0
- 新增
POST、HEAD方法,支持头部(Header)、状态码(如200 OK)、多类型数据(图片等) - 每次请求都要重新建立 TCP 连接,用完就关闭
- 问题:效率低,因为频繁建立和关闭连接
- 新增
-
HTTP/1.1(最常用)
- 长连接:一个 TCP 连接可发多个请求(省时间)
- 管线化(pipelining):允许连续发多个请求(但响应必须按顺序,可能阻塞)
- 新增
PUT、DELETE等方法,支持缓存控制(如Cache-Control) - 问题:队头阻塞(一个请求慢了,后面的等着)
-
HTTP/2(2015年)
- 二进制协议(更快解析,取代文本格式)。
- 多路复用:一个连接并行处理多个请求(解决队头阻塞)。
- 头部压缩(HPACK 算法节省流量)。
- 服务端推送(Server Push):主动推送资源给客户端。
-
HTTP/3
- 弃用 TCP,改用 QUIC 协议(基于 UDP,解决 TCP 的队头阻塞)。
- 连接更快(0-RTT 握手),更抗网络切换(如 WiFi 切 4G)。
五、浏览器的进程线程模型
浏览器进程
-
浏览器主进程:负责协调和管理浏览器的整体运行,包括界面显示、地址栏管理、文件下载、与其他进程的通信等。它是浏览器的核心控制进程,用户在浏览器中的大部分操作都由主进程进行调度和处理。
-
渲染进程:也称为浏览器内核进程,每个标签页通常对应一个渲染进程。主要负责 HTML 解析、CSS 样式计算、布局、绘制以及 JavaScript 脚本执行等与网页渲染相关的工作。多个标签页的渲染进程相互隔离,避免一个页面的崩溃影响其他页面。
-
GPU 进程:用于处理图形相关的任务,如页面的绘制、3D 动画、视频解码等。它通过硬件加速来提高图形处理性能,减少 CPU 的负担,使页面的渲染更加流畅。
-
网络进程:负责处理网络请求,包括发起 HTTP 请求、接收响应、处理缓存等。它与渲染进程协作,为网页加载所需的资源,如 HTML 文件、CSS 样式表、JavaScript 脚本、图片等。
-
插件进程:为浏览器中的插件(如 Flash 插件)提供运行环境。每个插件对应一个独立的插件进程,以确保插件的崩溃不会影响浏览器的其他部分。
渲染进程中的线程
-
主线程:也称为 UI 线程,是渲染进程中最重要的线程。它负责执行 HTML 解析、CSS 样式计算、布局生成、绘制页面等任务,同时还处理用户交互事件,如鼠标点击、键盘输入等。由于这些任务都是在主线程中顺序执行的,所以如果某个任务执行时间过长,就会导致页面卡顿,影响用户体验。
-
JavaScript 引擎线程:负责执行 JavaScript 代码。JavaScript 是单线程语言,在同一时间只能执行一个任务。JavaScript 引擎线程与主线程相互配合,当遇到 JavaScript 代码时,主线程会将控制权交给 JavaScript 引擎线程,待代码执行完毕后,再将控制权交回主线程。
-
合成线程:在页面渲染过程中,主线程会将页面的各个图层信息发送给合成线程。合成线程负责将这些图层进行合成,并将最终的图像发送给 GPU 进行绘制。合成线程可以在不阻塞主线程的情况下进行图层的合成操作,提高了页面的渲染效率。
-
光栅化线程:将页面的图层转换为位图,以便在屏幕上显示。它会根据合成线程的指令,将需要绘制的部分进行光栅化处理,并将生成的位图缓存起来。当页面需要更新时,可以直接从缓存中获取位图,提高绘制速度。
-
定时器线程:负责处理 JavaScript 中的定时器函数,如
setTimeout和setInterval。它会在指定的时间间隔后,将定时器的回调函数添加到任务队列中,等待 JavaScript 引擎线程执行。