MDN 上的 Web 性能定义:Web 性能是网站或应用程序的客观度量和可感知的用户体验。

  • 减少整体加载时间:减小文件体积、减少 HTTP 请求、使用预加载
  • 使网站尽快可用:仅加载首屏内容,其它内容根据需要进行懒加载
  • 平滑和交互性:使用 CSS 替代S动画、减少U1 重绘
  • 感知表现:你的页面可能不能做得更快,但你可以让用户感觉更快。耗时操作要给用户反馈,比如加载动画、进度条、骨架屏等提示信息
  • 性能测定:性能指标、性能测试、性能监控持续优化

思路

  • 从发出请求到收到响应的优化,比如 DNS 查询、HTTP 长连接、HTTP 2、HTTP 压缩、HTTP 缓存等。
  • 关键渲染路径优化,比如是否存在不必要的重绘和回流。
  • 加载过程的优化,比如延迟加载,是否有不需要在首屏展示的非关键信息,占用了页面加载的时间。
  • 资源优化,比如图片、视频等不同的格式类型会有不同的使用场景,在使用的过程中是否恰当。
  • 构建优化,比如压缩合并、基于 webpack 构建优化方案等。

Dev Tools

  • 网络请求面板
  • coverage 面板,测试代码覆盖率
  • Memory 面板,测试内存泄漏
  • Performance 面板,测试页面性能
  • FPS 面板,测试页面 FPS
  • Performance Monitor 面板,实时 测试页面性能监控

lighthouse 工具

Web Page Test 工具

最佳实践:

  1. 减少 DNS 查找:每次主机名的解析都需要一次网络往返,从而增加了请求的延迟时间,同时还会阻塞后续的请求。
  2. 重用 TCP 连接:尽可能的使用持久连接,以消除因 TCP 握手和慢启动导致的延迟。
  3. 减少HTTP 重定向:HTTP 冲定向需要额外的 DNS 查询、TCP 握手等非常耗时,最佳的重定向次数为0。
  4. 压缩传输的资源:比如 Gzip、图片压缩。
  5. 使用缓存:比如 HTTP 缓存、CDN 缓存、Service Worker 缓存。
  6. 使用 CDN(内容分发网络):把数据放在离用户地理位置更近的地方,可以明显减少每次TCP 连接的网络延迟,增大吞吐量。
  7. 删除没有必要请求的资源。
  8. 在客户端缓存资源:缓存必要的应用资源,避免每次都重复请求相同的内容,例如多图片下载可以考虑使用缓存。长窝数值可以调数樨洗尺寸
  9. 传输前先压缩:传输数据之前应该先压缩应用资源,把要传输的字节减少到最小。
  10. 消除不必要的请求开销:减少请求的 HTTP 首部数据(比如 HTTP Cookie)。
  11. 并行处理请求和响应:请求和响应的排队都会导致延迟,可以尝试并行的处理请求和响应(利用多个 HTTP1.1 连接实现并行下载,在可能的情况下使用 HTTP 管道计数)。
  12. 针对协议版本采取优化措施。升级到 HTTP2.0。 13.根据需要采用服务端渲染方式。这种方式可以解决 SPA 应用首屏渲染慢的问题。
  13. 采用预渲染的方式快速加载静态页面。页面渲染的极致性能,比较适合静态页面。

# 减少 DNS 解析

使用 dns-prefetch 预加载 DNS,减少 DNS 解析时间。

<link  rel="dns-prefetch" href="//www.example.com" />

image-20241002182441021

# HTTP2

# 二进制协议

HTTP/1.1 版的头信息肯定是文本(ASCII 编码),数据体可以是文本,也可以是二进制。

HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧"(frame):头信息帧和数据帧。

二进制协议的一个好处是,可以定义额外的帧。HTTP/2 定义了近十种帧,为将来的高级应用打好了基础。如果使用文本实现这种功能,解析数据将会变得非常麻烦,二进制解析则方便得多。

# 多路复用

HTTP/2 复用 TCP 连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了"队头堵塞"。

举例来说,在一个 TCP 连接里面,服务器同时收到了 A 请求和 B 请求,于是先回应A 请求,结果发现处理过程非常耗时,于是就发送 A 请求已经处理好的部分,接着回应B请求,完成后,再发送 A 请求剩下的部分。

这样双向的、实时的通信,就叫做多工 (Multiplexing)

这是一个对比 HTTP1 和 HTTP2 资源加载的在线实例:https://http2.akamai.com/demo

实测http1和http2性能对比,并分析Network的Timing图 (opens new window)

# 数据流

因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的回应。因此,必须要对数据包做标记,指出它属于哪个回应。

HTTP/2 将每个请求或回应的所有数据包,称为一个数据流(stream)。每个数据流都有一个独一无二的编号。数据包发送的时候,都必须标记数据流ID,用来区分它属于哪个数据流。

另外还规定,客户端发出的数据流,ID一律为奇数,服务器发出的,ID 为偶数。

数据流发送到一半的时候,客户端和服务器都可以发送信号(RST_STREAM帧),取消这个数据流。1.1 版取消数据流的唯一方法,就是关闭 TCP 连接。这就是说,HTTP/2 可以取消某一次请求,同时保证 TCP 连接还打开着,可以被其他请求使用。

客户端还可以指定数据流的优先级。优先级越高,服务器就会越早回应。

# 头信息压缩

HTTP 协议不带有状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。

HTTP/2 对这一点做了优化,引入了头信息压缩机制 (header compression)。一方面,头信息使用 gzip 或 compress 压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引 号,这样就提高速度了。

# 服务器推送

HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送 (server push)

常见场景是客户端请求一个网页,这个网页里面包含很多静态资源。正常情况下,客户端必须收到网页后,解析 HTML 源码,发现有静态资源,再发出静态资源请求。其实,服务器可以预期到客户端请求网页后,很可能会再请求静态资源,所以就主动把这些静态资源随着网页一起发给客户端了。

# HTTP 响应数据压缩

# 使用 gzip 压缩文本

请求头

accept-encoding: gzip, deflate, br, zstd

响应头

content-encoding: gzip

# 图片压缩

# HTTP 请求数据压缩

# 头部数据压缩

HTTP 协议不带有状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。

HTTP/2 对这一点做了优化,引入了头信息压缩机制 (header compression)。一方面,头信息使用 gzip 或 compress 压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引1号,以后就不发送同样字段了,只发送索引 号,这样就提高速度了。

# 请求体数据压缩

前面我们介绍了 HTTP 协议中的 Accept -Encoding/Content -Encoding 机制。这套机制可以很好地用于文本类响应正文的压缩,可以大幅减少网络传输,从而一直被广泛使用。但 HTTP 请求的发起方(例如浏览器),无法事先知晓要访问的服务端是否支持解压,所以现阶段的浏览器没有压缩请求正文。

有一些通讯协议基于 HTTP 做了扩展,他们的客户端和服务端是专用的,可以放心大胆地压缩请求正文。例如 WebDAV 客户端就是样。

实际 Web 项目中,会存在请求正文非常大的场景,例如发表长篇博客,上报用于调试的网络数据等等。这些数据如果能在本地压缩后再提交,就可以节省网络流量、减少传输时间。本文介绍如何对 HTTP 请求正文进行压缩,包含如何在服务端解压、如何在客户端压缩 两个部分。

开始之前,先来介绍本文涉及的三种数据压缩格式:

  • DEFLATE,是一种使用 Lempel-Ziv 压缩算法(LZ77)和哈夫曼编码的压缩格式。详见 RFC 1951;

  • ZLIB,是一种使用 DEFLATE 的压缩格式,对应 HTTP 中的 Content-Encoding: deflate。详见 RFC 1950;

  • GZIP,也是一种使用 DEFLATE 的压缩格式,对应 HTTP 中的 Content-Encoding: gzip。详见 RFC 1952;

Content-Encoding 中的 deflate,实际上是 ZLIB。为了清晰,本文将 DEFLATE 称之为 RAW DEFLATE, ZLIB 和 GZIP 都是 RAW DEFLATE 的不同 Wrapper。

上次更新: 11/6/2024, 4:10:52 PM