🤔从输入URL回车之后到页面渲染全过程到前端性能优化相信不少掘U们在面试过程中都会碰到这样一道经典题目:《从输入URL
前言
相信不少掘U们在面试过程中都会碰到这样一道经典题目:《从输入URL回车之后到页面渲染全过程》。而这道题目之所以经典,就在于只有我们知道了在这个过程中都有哪些步骤,我们才能够去针对每一步尽可能地进行性能优化。最近在准备秋招,查找了一些资料,对这个问题有了更深刻的体会。
正文
下面是从输入URL回车之后到页面渲染全过程的大致流程:
接下来我们逐步解析每个步骤:
一、DNS解析获取IP地址
1.什么是DNS
DNS,即域名系统(Domain Name System),是互联网的重要组成部分。它的主要作用是将人类易读的域名(如www.example.com
)解析为机器可读的IP地址(如192.0.2.1
),因为计算机通过IP地址来识别和访问彼此。
2.DNS的工作原理
DNS解析是一个递归查询的过程,通常包括以下步骤:
- 客户端查询DNS缓存:当用户在浏览器中输入URL时,浏览器会首先检查本地DNS缓存中是否有对应的域名解析记录。
- 操作系统DNS缓存:如果浏览器缓存中没有记录,操作系统会进一步检查它自己的DNS缓存。
- 路由器DNS缓存:如果操作系统缓存也没有记录,请求会被发送到用户所在网络的路由器,该路由器可能也有DNS缓存。
- 递归DNS服务器:如果在以上缓存中都没有找到记录,路由器会将请求转发给ISP(Internet Service Provider)提供的递归DNS服务器。这个服务器会逐级查询其他DNS服务器,直到找到对应的IP地址。
3.DNS服务器的层级结构
DNS服务器是分层架构的,主要分为以下几类:
- 根DNS服务器(Root Name Servers) :它是整个DNS系统的顶层,只会将请求指向顶级域名服务器。
- 顶级域名服务器(TLD Name Servers) :管理着某一个顶级域(如
.com
、.org
等)的域名解析,将请求进一步指向特定域的权威DNS服务器。 - 权威DNS服务器(Authoritative Name Servers) :最终给出具体域名的IP地址。
4. DNS解析的过程
以用户在浏览器中输入www.example.com
为例,DNS解析的过程如下:
- 查询浏览器缓存:浏览器首先在它的缓存中查找
www.example.com
对应的IP地址。如果找到了,则直接使用该IP地址发起请求;如果没找到,继续下一步。 - 查询操作系统缓存:浏览器会将请求发送到操作系统的DNS解析器(Resolver),查看操作系统层面的DNS缓存。如果找到结果,直接返回给浏览器;如果没有,则查询本地网络配置。
- 查询路由器缓存:如果操作系统缓存中也没有结果,DNS请求会被转发到用户所在网络的路由器,路由器也可能会缓存常用的DNS查询结果。如果找到,则返回IP地址;如果找不到,继续下一步。
- 递归查询开始:路由器将请求发送到ISP的递归DNS服务器,该服务器会负责接下来的递归查询过程。首先,递归DNS服务器会向根DNS服务器发送查询请求,根DNS服务器不会直接返回IP地址,而是返回管理
.com
域的顶级域名服务器的地址。 - 查询顶级域名服务器:递归DNS服务器接收到根服务器的回复后,继续向顶级域名服务器(例如
.com
服务器)发送查询请求。顶级域名服务器返回管理example.com
的权威DNS服务器的地址。 - 查询权威DNS服务器:最终,递归DNS服务器向权威DNS服务器发送查询请求,权威DNS服务器返回
www.example.com
的IP地址。递归DNS服务器将这个结果返回给路由器,路由器再返回给操作系统,最后由操作系统返回给浏览器。 - 浏览器缓存结果并发起请求:浏览器获取到IP地址后,会缓存这次的DNS解析结果,随后使用该IP地址向目标服务器发起HTTP请求。

了解了DNS解析获取IP地址的过程之后,我们可以发现,DNS解析的时间成本主要体现在两个方面:
递归查询的延迟
和网络传输延迟
所以针对DNS解析,我们可以采取以下措施来实现性能优化:DNS缓存:合理设置DNS记录的TTL(Time To Live),让更多的DNS查询结果能被缓存,减少递归查询的次数。
DNS预解析:在HTML的
<head>
部分可以使用<link rel="dns-prefetch" href="//example.com">
,让浏览器在实际需要之前预先解析域名,减少未来的DNS查询时间。CDN的使用:使用内容分发网络(CDN)将网站的内容分布在全球多个服务器上,可以减少因地域差异造成的DNS查询延迟,同时CDN自身的DNS服务器往往速度更快。
HTTP/2/3和DNS解析:新一代的HTTP协议在减少连接数和优化请求方面有更好的表现,也间接地减少了DNS查询的次数。
二、三次握手与服务器建立TCP连接
TCP(传输控制协议)
是一种面向连接的、可靠的、基于字节流的传输层通信协议。在确定目标服务器服务器的IP
地址后,则经历三次握手建立TCP
连接:
三次握手原理:
第1次握手:客户端发送一个带有SYN(synchronize)标志的数据包给服务端;
第2次握手:服务端接收成功后,回传一个带有SYN/ACK标志的数据包传递确认信息,表示我收到了;
第3次握手:客户端再回传一个带有ACK标志的数据包,表示我知道了,握手结束。
其中:SYN标志位数置1,表示建立TCP连接;ACK标志表示验证字段。

三、发送HTTP请求
当建立tcp
连接之后,就可以在这基础上进行通信,浏览器发送 http
请求到目标服务器。
请求的内容包括:请求行、请求头、请求体

四、服务器响应请求
当服务器接收到浏览器的请求之后,就会进行逻辑操作,处理完成之后返回一个HTTP
响应消息,包括:状态行、响应头、响应正文

在服务器响应之后,由于现在http
默认开始长连接keep-alive
,当页面关闭之后,tcp
链接则会经过四次挥手完成断开。
五、四次挥手断开连接
四次挥手原理:
第1次挥手:客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入FIN_WAIT_1状态;
第2次挥手:服务端收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),服务端进入CLOSE_WAIT状态;
第3次挥手:服务端发送一个FIN,用来关闭服务端到客户端的数据传送,服务端进入LAST_ACK状态;
第4次挥手:客户端收到FIN后,客户端t进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,服务端进入CLOSED状态,完成四次挥手。
其中:FIN标志位数置1,表示断开TCP连接。

在二、三、四、五步骤中,主要涉及到的是客户端从服务端获取资源,前端对其进行性能优化可以从以下几个方面:
TCP连接的优化
使用HTTP/2或HTTP/3
- HTTP/2:支持多路复用,即在一个TCP连接上可以同时发送多个请求和响应。这种机制减少了传统HTTP/1.1中每个请求都需要单独建立连接的开销,从而降低了延迟并提升了加载速度。
- HTTP/3:基于UDP协议,避免了TCP的连接建立和断开过程,减少了握手的延迟。HTTP/3还内置了TLS加密,更安全且更快。
启用持久连接(Keep-Alive)
- Keep-Alive:HTTP/1.1默认开启Keep-Alive,它允许在同一个TCP连接上处理多个HTTP请求,从而减少了重复建立和关闭连接的开销。确保服务器配置支持长连接,并合理设置Keep-Alive的超时时间,避免过多的连接占用资源。
HTTP请求的优化
减少HTTP请求的数量
- 资源合并:将多个CSS、JavaScript文件合并为一个文件,减少HTTP请求次数。
- 图片精灵(CSS Sprites) :将多个小图标合并成一张大图片,通过CSS定位显示不同部分,从而减少HTTP请求。
- 懒加载(Lazy Loading):对不立即需要的资源(如图片、视频)使用懒加载技术,只在用户滚动到页面特定位置时再加载这些资源,减少初始页面加载的请求数量。
使用内容分发网络(CDN)
- CDN加速:通过CDN将静态资源分发到全球各地的服务器节点,用户可以从距离最近的节点获取资源,减少加载时间和延迟。
启用压缩
- Gzip或Brotli:在服务器上启用Gzip或Brotli压缩,减少传输的文件大小。特别是对于文本类资源(如HTML、CSS、JavaScript),压缩率可以达到50%以上。
缓存策略优化
- 浏览器缓存:通过设置合适的HTTP缓存头(如
Cache-Control
、ETag
、Expires
),可以利用浏览器缓存来减少对服务器的请求次数,并提升页面加载速度。- 版本控制:使用文件名中的版本号(如
app.v1.js
),使得当文件更新时,浏览器能够重新获取新的资源,而没有变化的资源继续使用缓存。
六、浏览器解析请求到的资源与渲染
当浏览器接收到服务器响应的资源后,首先会对资源进行解析:
- 查看响应头的信息,根据不同的指示做对应处理,比如重定向,存储cookie,解压gzip,缓存资源等等
- 查看响应头的 Content-Type的值,根据不同的资源类型采用不同的解析方式
关于页面的渲染过程如下:
解析HTML
浏览器首先会解析HTML文档,生成一个称为DOM(Document Object Model)树的结构。
DOM树是由HTML文档的元素节点构成的,根节点是<html>
元素。每个HTML标签(如<div>
、<p>
)都对应一个DOM节点,节点之间存在父子关系。
- 顺序解析:HTML文档是从上到下顺序解析的。当浏览器遇到HTML标签时,它会将标签转化为DOM节点并添加到DOM树中。
解析CSS
同时,浏览器也会解析CSS文件和内嵌在HTML中的样式,生成一个CSSOM(CSS Object Model)树。
CSSOM树是由CSS选择器和其对应的样式规则构成的。它与DOM树独立存在,但二者会在渲染树构建过程中结合。CSSOM树考虑了样式的继承和层叠关系,决定了最终样式的计算顺序。
- 阻塞渲染:CSS的解析会阻塞渲染树的构建,因为渲染树的生成需要知道每个DOM节点的样式。浏览器会在CSSOM树生成后才开始构建渲染树。
解析JavaScript
JavaScript的解析和执行会影响DOM树和CSSOM树的生成:
- 阻塞行为:如果JavaScript在HTML文档中直接引用,浏览器在解析到JavaScript时会暂停HTML解析,直到JavaScript执行完成。这会影响页面的渲染速度。
- 异步加载:可以使用
<script async>
或<script defer>
来异步或延迟加载JavaScript,从而避免阻塞HTML的解析。
渲染树的生成
渲染树(Render Tree)结合了DOM树和CSSOM树的信息,它是显示页面内容的基础:
- 非可视节点排除:渲染树只包含可见的DOM节点,例如
<head>
标签和display: none
的元素不会出现在渲染树中。 - 结合样式信息:渲染树节点不仅包含DOM树的内容,还结合了CSSOM树中的样式信息,确定了元素的最终显示样式。
布局计算
在渲染树生成后,浏览器会进行布局计算(Layout),即确定每个元素在页面中的确切位置和大小。
- 流式布局模型:浏览器通常采用流式布局模型,从根节点开始,依次计算每个节点的几何属性。
- 依赖关系:如果一个元素的位置或大小依赖于另一个元素,则浏览器会先计算依赖元素的布局,再计算当前元素。
在这个步骤中,我们可以进行这些性能优化:
优化 CSS 加载:
避免使用
@import
来引入 CSS 文件,因为它会阻塞页面渲染。- 将关键的 CSS 样式放在
<style>
标签内直接嵌入 HTML 文档的<head>
部分,以尽快提供初始样式,减少无样式内容的闪烁(FOUC)。优化 JavaScript 加载和执行:
- 尽量将非关键的 JavaScript 脚本放在页面底部加载,使用
async
或defer
属性来异步加载脚本,减少对页面渲染的阻塞。- 对于大型的 JavaScript 应用,进行代码分割和懒加载,只在需要时加载特定的模块。
优化 DOM 操作:
- 减少不必要的 DOM 操作,避免频繁的添加、删除或修改 DOM 节点。
- 批量处理 DOM 操作,而不是逐个进行,以减少重绘和重排。
七、页面的绘制与合成
绘制(Painting)
布局完成后,浏览器会开始绘制页面,即将渲染树中的每个节点转换为屏幕上的像素:
- 分层绘制:浏览器会根据元素的层叠上下文(如
z-index
)将页面分为多个图层,分别进行绘制。每个图层包含元素的背景、边框、文本、图像等内容。 - 绘制顺序:绘制是从后到前的顺序执行,即先绘制背景,再绘制内容,最后绘制前景。
合成(Compositing)
当所有图层绘制完成后,浏览器会进行合成操作,将各个图层组合在一起并显示在屏幕上:
- 图层合成:一些复杂的页面可能涉及多个图层的合成,例如具有固定定位的元素、CSS动画或3D变换的元素。浏览器会对这些图层进行合成,确保最终显示的页面符合设计预期。
- GPU加速:现代浏览器会利用GPU(图形处理单元)来加速合成过程,尤其是对于复杂的动画或大面积的重绘,GPU能够显著提升渲染效率。
在这一步骤中,我们可以进行如下优化:
绘制(Painting)阶段:
- 减少图层数量:避免不必要的
z-index
设定,以减少图层的划分,降低绘制的复杂度。- 优化绘制区域:仅更新需要更改的部分,而不是整个页面,例如使用
requestAnimationFrame
来精确控制重绘区域。合成(Compositing)阶段:
- 避免频繁的图层更改:尽量减少会导致图层属性频繁变动的操作,如不必要的元素位置或尺寸变化。
- 合理使用 GPU 加速:对于复杂的动画和图形操作,使用
will-change
属性提前告知浏览器可能的变化,以触发 GPU 加速。但要注意不要过度使用,以免造成资源浪费。- 优化动画效果:使用硬件加速友好的动画属性,如
transform
和opacity
,而避免使用会导致重绘和重排的属性,如width
和height
。- 减少重绘和回流:例如,将复杂的样式更改集中在一次操作中,而不是分散多次。
例如,如果有一个元素需要频繁移动并带有动画效果,可以将其设置为
position: fixed
或position: absolute
以创建单独的图层,并使用transform
进行位置变换,同时设置will-change: transform
来利用 GPU 加速,从而提高动画的流畅性和性能。![]()
总结
对平常遇到的问题,不能只是了解问题的答案,更应该思考问题背后的底层逻辑,从而融会贯通。以上就是我通过《从输入URL回车之后到页面渲染全过程》到理解如何进行部分前端性能优化的过程的浅薄理解。如果有错误,欢迎大家在评论区指出。
创作不易,如果对各位产生了一些帮助,麻烦点赞收藏一下吧!
参考资料:
vue3js.cn/interview/h… vue3js.cn/interview/h… bbs.huaweicloud.com/blogs/23366…
转载自:https://juejin.cn/post/7403223282181390347