背景
目前越来越多的网站和应用程序依赖于前端技术。在前端技术中,浏览器缓存是一项重要的优化技术,可以显著提高网站的性能和用户体验。通过利用浏览器缓存,可以减少页面请求次数,降低服务器的负载,提高网站的访问速度,从而提高用户的体验。
在本文中,我们将探讨前端浏览器缓存的优化技术以及它们的实现方式。
如果您需要进一步的协助或有任何问题,请随时提问!希望这篇文章对大家有所帮助。
什么是浏览器缓存
浏览器缓存是指浏览器在第一次访问一个网页时,将页面的各种资源文件(如样式表、JavaScript文件、图片等)缓存到本地磁盘上。当用户再次访问该网页时,浏览器会先检查本地缓存是否存在对应的资源文件,如果存在
且未过期
,就直接从本地加载资源文件,而不是重新下载。
浏览器缓存优化是一种可以显著提高网站性能的技术,因为它减少了网络请求次数,降低了服务器的负载,同时也缩短了用户等待页面加载的时间。
浏览器缓存机制
浏览器缓存位置
当我们访问页面后,再次刷新页面,打开浏览器开发者工具 Network,可以发现很多资源文件都来自缓存。例如:
或者:
浏览器缓存分为四个位置:
1、Memory Cache
内存缓存
,将请求结果缓存到内存中,读取速度快。
Memory Cache是指浏览器在内存中缓存的一些静态资源,例如HTML、CSS、JavaScript文件等。由于内存读取速度非常快,因此浏览器将这些资源缓存在内存中,以便下次访问时能够更快地获取这些资源。缓存在内存中的资源可以非常快速地被访问和加载,但是内存空间非常有限,所以这些资源只会在浏览器打开期间保存。
2、Disk Memory
硬盘缓存
,将请求结果缓存到硬盘中,读取速度相对慢。
Disk Cache 是指浏览器在磁盘中缓存的一些静态资源,例如HTML、CSS、JavaScript文件等。与 Memory Cache 不同,Disk Cache 中的资源可以被长时间保存,即使浏览器关闭后也可以再次访问。但是,由于磁盘读取速度相对内存较慢,所以从磁盘中读取缓存的资源会比从内存中读取慢一些。通常情况下,浏览器会根据 HTTP 响应头中的缓存控制信息来决定是否将数据缓存到硬盘中。
硬盘缓存比内存缓存读取速度慢,读取需要对硬盘进行I/O操作,会导致重新解析缓存内容,造成读取路的复杂。
我们常见最多的就是 Memory Cache 与 Disk Memory。
区别 | Memory Cache | Disk Memory |
---|---|---|
存储资源 | 一般脚本、图片、字体 | 一般非脚本css等 |
存储时效 | 进程关闭清除 | 不受进程关闭影响,可以缓存较长时间 |
存储空间 | 小 | 大 |
读取速度 | 快 | 慢 |
CSS文件加载一次就可以渲染,不会频繁的读取,存储在Disk Cache;js脚本可能会随时会被执行,存储在Memory Cache,若存储在硬盘中,会因为I/O开销大导致浏览器失去响应。
3、Server Worker
可以让网站在用户离线时继续运行,并且可以控制页面的缓存。
Service Worker 是一种运行在后台的 JavaScript 脚本,可以控制网络请求和响应。Service Worker可以缓存页面中的资源,例如 HTML、CSS、JavaScript文件、图像、字体等,可以使网站在离线状态下仍能够访问。
与 Memory Cache和Disk Cache不同,Service Worker Cache是一种持久化的缓存,即使用户关闭浏览器后,这些缓存也可以被保留下来。
4、Push Cache
仅适用于HTTP/2,用于缓存推送的响应结果。
推送缓存,是HTTP/2的内容,并没有严格执行HTTP头部的缓存指令。在Server Worker、Memory Cache、Disk Cache都没有命中的时候,它会被使用。在Session中存在,Session结束就会被释放,缓存时间短暂。
总之,Memory Cache 和 Push Cache 缓存都是相对较短的缓存,一般只在浏览器当前打开的标签页中有效。而 Disk Cache 和 Service Worker 缓存则可以在用户关闭浏览器后仍然有效。
以下是将浏览器缓存位置四类的区别按照表格进行展示:
缓存位置 | 作用 | 存储时机 | 过期机制 |
---|---|---|---|
内存缓存 | 临时存储,提高用户体验 | tab页面关闭后自动清除 | 无过期机制,由浏览器自动控制 |
磁盘缓存 | 加快网页打开速度 | 长期存储,直到缓存满或过期 | 由服务器设置响应头进行控制 |
Service Worker 缓存 | 支持离线访问和更好的缓存控制 | 由开发者手动控制 | 由开发者手动控制 |
Push Cache | 减少网络传输和服务器负载 | 由服务器设置响应头进行控制 | 由服务器设置响应头进行控制 |
说完浏览器缓存位置四类的区别,我们再来说一下prefetch cache 预取缓存。
prefetch cache 预取缓存
预取缓存
是在页面加载时自动预取相关资源,以便在用户需要时能够快速加载。它主要用于加速页面和资源的加载速度,减少用户等待的时间。
预取缓存的实现通常通过添加 link
标签来完成。通过标记prefetch
实现预加载,被标记为 prefetch 的资源会在浏览器空闲的时间加载。例如,可以在页面的头部添加以下代码:
html
<link rel="prefetch" href="https://example.com/image.jpg">
预取缓存(Prefetch Cache)与浏览器缓存位置四类有着一定的区别,下面是它们之间的主要区别:
区别 | 预取缓存 | 浏览器缓存位置四类 |
---|---|---|
作用 | 加速页面和资源的加载速度 | 减少网络传输和服务器负载,提高用户体验 |
缓存位置 | 预取缓存中 | 内存缓存、磁盘缓存、HTTP 缓存、Service Worker 缓存 |
缓存内容 | 缓存页面和资源的 URL | 缓存页面和资源的实际内容 |
控制方式 | 通过添加 link 标签来控制 | 通过 HTTP 头信息来控制 |
浏览器缓存分类
浏览器缓存分为强缓存和协商缓存两种。
强缓存
强缓存是指直接从缓存中读取资源,而不需要向服务器发送请求,一般通过设置响应头来实现。
常见响应头设置的缓存标识有
Cache-Control
和Expires
。
Cache-Control字段用于控制缓存的行为,它可以设置多个指令,常用的有以下几个:
指令 | 作用 |
---|---|
public | 允许所有用户缓存 |
private | 只允许私有缓存,比如浏览器缓存 |
no-cache | 不直接使用缓存,需要先向服务器验证资源是否过期 |
no-store | 不缓存资源 |
例如,设置强缓存的有效期为3600秒,可以添加如下的HTTP响应头:
arduino
Cache-Control: max-age=3600
Expires字段用于设置资源的过期时间,它的值为一个GMT格式的日期时间字符串。如果当前时间在Expires字段的值之前,那么缓存资源就是有效的。
例如,设置缓存的过期时间为2024年1月1日,可以添加如下的HTTP响应头:
yaml
Expires: Mon, 1 Jan 2024 00:00:00 GMT
协商缓存
协商缓存则是先向服务器发送请求,服务器根据请求头中的信息判断资源是否可以缓存,并返回相应的状态码和响应头信息,以告诉浏览器是否可以使用缓存。如果文件没有发生变化,则返回304状态码。
协商缓存可以通过设置HTTP响应头中的
Last-Modified
和ETag
字段来实现。
Last-Modified字段用于记录资源的最后修改时间,它的值为一个GMT格式的日期时间字符串。当浏览器再次请求该资源时,会在请求头中添加If-Modified-Since字段,其值为上一次请求返回的Last-Modified字段的值。如果资源的最后修改时间比If-Modified-Since字段的值要新,那么服务器就返回最新的资源,否则返回304状态码。
例如,设置资源的最后修改时间为2022年1月1日,可以添加如下的HTTP响应头:
yaml
Last-Modified: Fri, 1 Jan 2022 00:00:00 GMTyaml
ETag字段用于记录资源的唯一标识符,它的值可以是一个哈希值或者一个版本号。当浏览器再次请求该资源时,会在请求头中添加If-None-Match字段,其值为上一次请求返回的ETag字段的值。如果资源的唯一标识符和If-None-Match字段的值相同,那么服务器就返回304状态码,否则返回最新的资源。
例如,设置资源的唯一标识符为"abc123",可以添加如下的HTTP响应头:
vbnet
ETag: "abc123"
强缓存和协商缓存区别
注意,强缓存和协商缓存是HTTP协议中的两种不同的缓存机制,它们的具体实现方式可能因浏览器和服务器的不同而有所差异。
特征 | 强缓存 | 协商缓存 |
---|---|---|
缓存判断位置 | 客户端浏览器 | 服务端 |
缓存命中时机 | 在第一次请求资源时,服务器将资源返回给客户端,并在响应头中添加缓存标识 | 在第一次请求资源时,服务器将资源返回给客户端,并在响应头中添加缓存标识,客户端在后续请求时将该标识带回给服务器,服务器根据标识判断是否命中缓存 |
命中缓存的响应码 | 200(from cache) | 304(not modified) |
缓存过期时间 | 可以通过设置Expires或Cache-Control响应头来指定 | 可以通过设置Last-Modified或Etag响应头来指定 |
缓存优先级 | 高 | 低 |
缓存机制 | 客户端控制 | 服务器控制 |
强缓存和协商缓存的区别,也说明了两者在浏览器缓存机制下也存在着不同,见下面浏览器缓存过程。
浏览器缓存过程(很重要
)
搞明白这个,基本就摸清了浏览器缓存的机制。
浏览器缓存的过程一般分为以下几步:
- 浏览器第一次(或禁止缓存)请求加载资源,向服务端发送请求,服务器返回200,浏览器将最新的资源从服务器下载。如果服务端配置了 Headers,服务端会在请求返回时,随 Response Headers 一起返回给浏览器,浏览器会将 Response Headers 与返回时间一起缓存。
- 浏览器再次发起请求加载资源的时候,会比较与上一次下载资源的时间差,如果没有超过 Cache-Control 设置的 max-age ,则没有过期,此时就会从本地缓存读取资源。如果浏览器不支持 HTTP1.1,那么则会用 Expires 判断是否过期(这一过程称为强缓存)。
- 如果对比时间发现已过期,或者没有强缓存标识。服务器则会查看请求的 Headers 中 If-None-Match 的值,与该请求资源的 Etag 做比较,如果相同则代表资源没有发生改变,返回304。否则,直接返回新的资源,并返回200(这一过程称为协商缓存)。
- 如果服务器收到的请求 Headers 中,没有 Etag 值,则会读取 If-Modified-Since 和被请求文件的最后修改时间做对比,如果相同则代表没有发生改变,返回304。否则,直接返回新的资源,并返回200(这一过程称为协商缓存)。
下载资源的同时,在 Response Headers 中会携带 Etag (资源的唯一标识,资源发生改变,标识也随之改变)、Last-Modified(资源文件最后一次更改时间),而浏览器会把这两个保存下来。
而向服务端发送资源请求时,在 Request Headers 中,会把浏览器下载资源时缓存的 ETag 放到 If-None-Match 中,把保存的 Last-Modified 放到 If-Modified-Since 中发给服务端。
Etag -> If-None-Match
Last-Modified -> If-Modified-Since
DNS缓存与CDN缓存
除了浏览器缓存以外,还有DNS缓存和CDN缓存也可以优化网站的访问速度。
DNS缓存
DNS缓存是指浏览器在访问网站时,会将网站的 DNS 解析结果缓存到本地缓存中。
这样下次访问同一域名时,就可以直接使用本地缓存中的解析结果,而不需要重新进行 DNS 解析,从而加快了网站的访问速度。
CDN缓存
CDN 缓存是指使用 CDN 服务提供商提供的缓存机制,将网站的静态资源文件缓存到CDN节点上,以加速用户访问网站的速度。
用户下次再访问同一资源时,就可以直接使用CDN节点中的缓存文件,而不需要重新下载文件,从而提高网站的访问速度。
缓存优化方案
为了更好地利用浏览器缓存,提高网站的访问速度,我们可以采取以下缓存优化方案:
打包构建优化
在打包构建时,可以为静态资源文件添加 Hash
值或版本号
,以便在文件内容变化时,可以强制浏览器重新加载新的文件。这样可以避免用户访问旧版本的文件,提高网站的访问速度。
例如,可以将样式表的 URL 修改版本号为:
ini
<link rel="stylesheet" href="style.css?v=1.0">
当样式表的内容发生变化时,只需要将 URL 中的版本号修改为 2.0,浏览器就会认为是一个新的文件,重新下载并缓存。
相比于版本号,Hash
更为优秀。
现在很多打包工具都默认配置了 Hash。其原理在于,只要文件内容不变,文件名就不会变,这样大大提高了静态文件缓存的使用寿命。
但是问题也就出现了,以上的这种优化,大都用于除 html 文件外的其它静态资源文件(比如说以.js、.png...后缀结尾的文件),而客户端在访问页面时,浏览器加载 html 是不会带上所谓的 Hash 或者版本号的。这就导致了很多时候服务端资源文件更新了,但是客户端由于缓存,还在使用之前老 html 文件加载的静态资源。那么这个问题如何处理呢?
首先,我们可以使用上面缓存过程 提到的强缓存
和协商缓存
,当服务端文件更新后,浏览器借用 Headers 标识来实现加载新资源。
另外,也可以通过设置 HTML 的 cache-control 实现。
html
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta http-equiv="Cache-Control" content="no-cache" />
这样可以告诉浏览器不要缓存 HTML 文件,从而确保每次访问页面时都会重新请求最新的 HTML 文件。但是需要注意的是,有些情况下这个方案会被服务端默认配置所覆盖。
我们还可以尝试添加 Etag
,但是这个方案有时有效果,有时没有效果。(实际中,某云厂家的资源未返回Etag)
因此,我们需要综合考虑,选择最适合项目的方案。在我的调试中,最稳妥的方案是:设置浏览器不缓存 HTML
。
设置浏览器不缓存,每次浏览器刷新都会拉取最新的 HTML 文件。而当 HTML 中加载的资源文件路径发生变化时,浏览器就会直接拉取最新的资源文件。但是这个方案也需要注意:由于每次都会请求新的 HTML,所以 HTML 只能加载资源路径,不能加载大量代码,否则页面渲染速度会变慢。
具体实现方案是,在服务端的 Nginx 中添加禁止缓存的 Header 配置,切记
只对 HTML 设置禁止缓存。配置如下:
bash
location / {
if ($request_filename ~* .*.(?:htm|html)$) {
add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
}
}
通过上述方案的使用,可以有效解决浏览器缓存资源带来的问题,确保用户访问的始终都是最新资源。
部署nginx代理配置(很有效
)
在处理前端浏览器缓存优化的过程中,我发现了一个问题:由于我们的项目使用微前端技术搭建,许多项目资源都在同一域名下,路径不同。如果按照之前的方式直接在 location
配置中添加 Header 缓存控制,所有项目的 HTML 都将被修改。
为了解决这个问题,我对路径进行了正则处理:
nginx
location ~ ^/static/(admin1|admin2|admin3)/ {
if ($request_filename ~* .*.(?:htm|html)$) {
add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
}
}
admin1|admin2|admin3 是各应用项目路径,可以添加和修改。
在上面的配置中,我使用了以下响应头:
private
只能被终端用户的浏览器缓存,不允许 CDN 等对其缓存。会影响到 CDN 缓存命中率,但在量较小的情况下,也可以禁用。no-cache
需要进行协商缓存,发送请求到服务器确认是否使用缓存。或者可以使用max-age=0
。no-store
完全禁用缓存。must-revalidate
与no-cache
类似,强制到服务端验证,适用于一些特殊场景,例如发送了校验请求,但发送失败了之类。proxy-revalidate
与上面类似,要求代理缓存服务验证有效性。
添加成功后,刷新浏览器效果如下:
到此为止,我已经成功地解决了我的项目中的浏览器缓存优化问题。
需要注意的是,配置完成后,页面第一次刷新后还是旧代码,这是正常情况(因为浏览器仍在使用之前的缓存),清除缓存然后重新刷新即可。
我按照上面配置,多次测试发现可行,目前为止没有发现问题。如果大家觉得还不稳妥,还可以通过添加 Expires
、Cache-Control
等响应头,控制浏览器缓存。配置完成后的效果如下:
压缩资源文件
另外,使用gzip
压缩功能可以将网站的资源进行压缩,从而减少文件的大小,加快文件的下载速度,提高网站的访问速度。
切记,使用 gzip 压缩功能。不仅构建打包工具需要配置,nginx 也需要配置。 nginx 通过在 http 块中添加以下配置实现:
bash
http {
gzip on; # 开启gzip压缩
gzip_min_length 1k; # 设置gzip压缩的最小文件大小
gzip_buffers 16 8k; # 设置gzip压缩的缓存区大小
gzip_http_version 1.0; # 设置gzip压缩使用的HTTP协议版本
gzip_types text/plain text/css application/json application/javascript application/xml; # 设置需要gzip压缩的文件类型
}
- 配置gzip压缩的文件类型,通过在gzip_types中添加需要进行gzip压缩的文件类型即可,例如上面的配置中,text/plain、text/css、application/json、application/javascript和application/xml这些文件类型会进行gzip压缩。
- 配置gzip压缩的缓存区大小和最小文件大小,通过在gzip_buffers和gzip_min_length中设置缓存区大小和最小文件大小来控制gzip压缩的性能和效果。
- 配置gzip压缩使用的HTTP协议版本,通过在gzip_http_version中设置gzip压缩使用的HTTP协议版本来控制gzip压缩的兼容性和稳定性。
配置成功如下:
其他常用缓存方案
使用DNS缓存和CDN缓存
除了浏览器缓存之外,DNS缓存和CDN缓存也是优化网站访问速度的重要手段。
DNS缓存可以加速域名解析,CDN缓存可以加速网站的内容分发。使用CDN可以将网站的静态资源文件缓存到CDN节点中,从而提高网站的访问速度。
在使用这些缓存技术的同时,还需要注意缓存的更新策略,避免出现过期缓存的情况。
我遇到过资源发布不更新问题,定位挺长时间,没有找到原因,最后找后端更新 CDN 才解决。
减少HTTP请求次数
减少HTTP请求次数可以减少网站的下载时间,从而提高网站的访问速度。可以通过合并文件、压缩文件等方式来减少HTTP请求次数。
减少重定向次数
重定向会增加网站的加载时间,从而降低网站的访问速度。可以通过合理的URL设计、避免链式重定向等方式来减少重定向次数。
除此之外还可以通过使用HTTP/2协议多路复用、二进制协议、头部压缩等功能、使用Memcached、Redis等缓存技术来优化网站的访问速度。
结语
总之,浏览器缓存优化可以大大提高网站的访问速度,减少服务器负载,提高用户体验。采取合适的缓存优化方案可以帮助我们更好地利用浏览器缓存,提高网站的性能和用户体验。