前端性能优化之性能瓶颈点,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 样式计算阶段和布局阶段。

往期回顾

相关推荐
禅思院2 小时前
Vite 开发环境下实现 YAML 配置热更新方案
前端·vue.js·前端框架
C_心欲无痕2 小时前
vue3 - toRefs将响应式对象转换为普通对象
前端·javascript·vue.js
sun0077002 小时前
macvlan解决vlan路由冲突
前端·chrome
小oo呆2 小时前
【自然语言处理与大模型】LangChainV1.0入门指南:AgentState介绍
前端·javascript·easyui
我是伪码农2 小时前
电子时钟案例
javascript·css·css3
weixin_462446232 小时前
Electron 禁止复制粘帖
前端·javascript·electron
@小张在努力2 小时前
Javascript中的闭包
开发语言·javascript·ecmascript
be or not to be2 小时前
CSS 文本样式与阴影整理笔记
前端·css·笔记
辛-夷2 小时前
js中如何改变this指向
开发语言·前端·javascript