前端性能优化之性能瓶颈点,Web 页面加载全流程解析

前言

对于 Web 页面首屏优化,有时候明明感觉自己做了很多事情,比如精简了首屏内容,合并了请求资源,优化了项目打包配置,也对图片进行了压缩,但最后的首屏时间还是没有降低下来,这是为什么呢?

要解答这个问题,需要先了解页面加载的全流程。

1、页面加载全流程

页面加载主要分为 3 个大的阶段:

  1. 客户端发起请求阶段。
  2. 服务器处理请求阶段。
  3. 客户端页面渲染阶段。

接下来针对这几个阶段进行详细介绍。

2、客户端请求阶段

客户端请求阶段可以分为下面 4 个子阶段:

  1. 查询本地缓存。
  2. DNS 解析。
  3. TCP 连接。
  4. HTTP 请求。

2.1 查询本地缓存

当用户在浏览器地址栏输入网址并按下 enter 键之后,浏览器会查询是否有可用的本地缓存,这个缓存分为两种:

  • 强缓存 :浏览器在加载资源时,会先根据请求头的 expires 或者 cache-control 判断是否命中强缓存,若命中,则直接从缓存中获取,不会发送请求到服务器。一般来说,对 jscss图片 等资源会启用强缓存。
  • 协商缓存 :浏览器会先发送请求到服务器,通过 If-Modified-Since / Last-ModifiedIf-None-Match / ETag 这两对 请求/响应头来判断资源是否命中协商缓存。
    • 若命中,服务器会返回 304 状态码,代表资源 Not Modified,自然也不会返回资源内容,而浏览器接收到这个状态码之后,会从缓存中加载该资源。
    • 若没命中缓存,会返回 200 状态码和新资源。

本地缓存是前端性能的瓶颈点之一,因为它可以大大提升静态资源的加载速度。

比如一个列表页的请求,DNS 解析时间 50ms + TCP 三次握手、TLS 协商 400ms + 数据返回 200ms,一个请求下来大约就 650 ms了,这个还是在强网(4G/5G/WIFI)情况下,如果在弱网(2G/3G)情况下,一个请求连接都需要 1.5s,而如果使用缓存的话,强缓存在几毫秒内就能完成,协商缓存的话,如果命中缓存,也只需要几十毫秒而已。

2.2 DNS 解析阶段

DNS 解析之所以会成为前端性能瓶颈点,是因为每进行一次 DNS 查询,都要走一遍手机 -> 移动信号塔 -> 认证 DNS 服务器的过程,一般来说,DNS 解析要耗费 50ms 左右(强网环境),而如果使用浏览器本地 DNS 缓存,则耗时会在 1ms 以内。

我们可以对浏览器或者 webview 配置DNS 预解析的接口,加快这一速度。

2.3 TCP 连接阶段

OSI七层网络通信参考模型中,这个主要是建立 TCP 连接主要是传输层做的工作,我们前端能做的事情十分有限,这里不多做介绍。

2.4 HTTP 请求阶段

这个阶段最大的瓶颈点来源于请求阻塞请求阻塞 指的是浏览器为了保证访问速度,会对同域名的资源做连接数限制,一般是 6 个,超过了就要排队,后续请求需要等最先返回请求的连接结束之后,才能开始请求。

对于请求阻塞,我们可以采取一些优化手段:

  1. 域名规划。可以看看当前页面需要用到哪些域名,然后最关键的首屏需要用到哪些域名,规划下域名的发送顺序。
  2. 域名散列 。前面说同域名有连接数限制,那我们多搞几个域名就好了呀,比如我们图片资源的地址原来是 image.example.com,我们做成支持 image0-5 的域名地址,比如 image0.example.com、image1.example.com、...image5.example.com,每次请求时随机选一个域名进行请求,这样在同时有 6 个域名可以使用的情况下,我们网页的最大并发连接数就来到了 36 个,当然,这个域名个数不是越多越好,太分散的话会遇到多域名无法缓存静态资源的问题。
  3. 使用HTTP2HTTP/2 只需要一个 TCP 连接就能实现多路复用,解决了 HTTP/1.1 的队头阻塞问题,而且还有 二进制分帧(Binary Framing)头部压缩(HPACK)服务器推送(Server Push)等优化,其效率会比 HTTP/1.1 高出不少。

3、服务端处理请求阶段

服务端处理请求阶段是指服务器收到请求后,从数据存储层取到数据,并经过一系列处理后返回给前端的过程。

其瓶颈点如下

  1. 是否做了数据缓存。
  2. 是否做了 Gzip 压缩。
  3. 是否有重定向。

3.1 数据缓存

  1. 对于后端来说,某一些特定的数据实时计算需要消耗大量的时间,比如排行榜数据,我们可以做 T+1 数据显示,也就是只显示前一天的数据,我们用定时任务把这些数据计算出来并缓存,然后取的时候不用实时计算速度就很快。
  2. 在前端层面,我们可以借助 Service Worker 的数据接口缓存能力,Service Worker 本质上是一个请求代理层,可以拦截和处理网络数据请求。
  3. 对于静态资源来说,我们可以使用 CDN 加速,它的基本思路是在各个地方放置缓存节点服务器,构造出一个智能虚拟网络,然后采用就近访问的原则,把用户的请求导向离用户最近的服务节点上,。

3.2 Gzip 压缩

  • 对于静态资源来说,可以全局在 nginx 上全局开启 gzip 压缩。
  • 对于接口数据来说,可以利用一些中间件或者自己手动实现 gzip 数据压缩,配合请求头 accept-encoding 和响应头去content-encoding 去实现。

3.3 重定向

  • 重定向指的是当网站资源地址发生变化后,程序会自动将请求导向另一个页面的过程。
  • 重定向主要包括 服务端 301、302 重定向meta 标签实现的重定向执行 JavaScript 通过 window.location 实现的重定向
  • 重定向会重新走一遍 DNS 查询 + TCP 连接 + TLS 协商 + 新的 HTTP 请求过程,用户需要耗费更多的时间才能看到最终的页面内容,严重影响前端性能。

4、客户端页面渲染阶段

当客户端拿到服务器返回的 HTML 内容后,就会开始进入页面解析和渲染阶段,它主要包括以下流程:

  • 构建 DOM 树 :浏览器是无法理解和使用 HTML,所以需要把 HTML 转换为浏览器能够理解的结构,即 DOM 树
  • 样式计算 :将 CSS 的样式来源中(包括 link 标签、style 标记内书写的 CSS 以及元素的 style 属性中内嵌的 CSS),按照优先级整合成浏览器可以理解的结构,即 styleSheets,然后计算出 DOM 节点中每个元素的具体样式。
  • 布局阶段:计算出 DOM 树中可见元素的几何位置。
  • 分层 :在渲染页面之前,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree),比如 3D 变换、页面滚动、或者使用了 z-index 的元素。
  • 绘制:生成绘制指令,然后按照顺序组成一个待绘制列表。
  • 光栅化:将上一步骤的待绘制列表,提交给合成线程。合成线程会把图层划分为图块,并按照视口附近的图块来优先生成位图,光栅化就是把图块生成位图的过程。
  • 合成和显示:当所有的图块都被光栅化之后,合成线程会提交给浏览器进程来绘制图块,浏览器会把页面内容绘制到内存中,最后显示在页面上。

构建 DOM 树的瓶颈点

  • HTML 标签是否语义化以及标签嵌套是否合理 :当我们书写的 HTML 标签不符合语义化标准时,浏览器需要花更多的时间去解析各个 DOM 标签的含义,同时也会使 页面的 SEO 变差。比如 br 没写结束标签,表格嵌套不标准,标签层次结构复杂等,浏览器遇到这些情况时,需要进行语法纠错,导致页面总的解析和渲染时间变长。
  • DOM 节点数量 :这个很好理解,DOM 节点越多,构建 DOM 树的时间越长,所以我们可以使用懒加载或者虚拟列表等手段减少 DOM 节点个数。
  • script 加载时机<script> 标签中的 JavaScript 可以获取并修改 DOM,所以在解析到 <script> 标签后,DOM 树的构建会被暂停,所以能延迟加载,就使用 deferasync 等属性异步加载,不阻塞 DOM 的解析。

CSS 样式计算中的瓶颈点

  • CSS 加载性能:是否做了公共样式抽离?是否删除了多余的 CSS?是否合理利用了内嵌CSS?
  • CSS 选择器性能:是否滥用了通配符选择器?选择层级是否嵌套过深?
  • CSS 属性性能 :是否使用了复杂或者不必要的 CSS 属性?是否滥用了 !important?
  • CSS 动画性能 :是否使用了 transformwill-changerequestAnimationFrame()等优化了动画?
  • CSS 渲染性能 :是否使用了 class 合并 DOM 的修改?是否让 DOM 元素脱离文档流?

关于 CSS 的性能优化,有兴趣详细了解的话可查看我之前写的 前端性能优化之CSS篇

布局中的瓶颈点

  • 如果在页面渲染过程运行时修改了一个元素的属性,这时候就会触发浏览器的重排或者重绘,增加布局时间。
  • 每次布局都需要计算整个 DOM 的几何位置,如果 DOM 节点很多,那么会花费很长的时间。

小结

页面加载主要分为 3 个大的阶段,包括客户端请求、服务端处理请求和客户端渲染阶段:

  • 客户端请求的瓶颈点:查询本地缓存、DNS 解析、TCP 连接和 HTTP 请求阶段。
  • 服务端处理请求阶段的瓶颈点:数据缓存、Gzip 压缩和重定向。
  • 客户端页面渲染阶段的瓶颈点:构建 DOM 树阶段、CSS 样式计算阶段和布局阶段。

往期回顾

相关推荐
再学一点就睡6 小时前
前端网络实战手册:15个高频工作场景全解析
前端·网络协议
C_心欲无痕6 小时前
有限状态机在前端中的应用
前端·状态模式
C_心欲无痕6 小时前
前端基于 IntersectionObserver 更流畅的懒加载实现
前端
candyTong6 小时前
深入解析:AI 智能体(Agent)是如何解决问题的?
前端·agent·ai编程
柳杉7 小时前
建议收藏 | 2026年AI工具封神榜:从Sora到混元3D,生产力彻底爆发
前端·人工智能·后端
weixin_462446237 小时前
使用 Puppeteer 设置 Cookies 并实现自动化分页操作:前端实战教程
运维·前端·自动化
CheungChunChiu7 小时前
Linux 内核动态打印机制详解
android·linux·服务器·前端·ubuntu
Irene19917 小时前
Vue 官方推荐:kebab-case(短横线命名法)
javascript·vue.js
GIS之路8 小时前
GDAL 创建矢量图层的两种方式
前端
2501_948195348 小时前
RN for OpenHarmony英雄联盟助手App实战:符文配置实现
javascript·react native·react.js