缓存是指将文件的副本存储在缓存(一个临时存储位置)中的过程,以便能够更快地访问。当浏览器缓存一个文件时,它会在用户设备上保存该文件的一个副本。这意味着下次用户访问同一页面时,浏览器可以从缓存中加载该文件,而不是从服务器重新下载。
浏览器缓存如何帮助页面加载
最佳性能的请求是不发出的请求
当用户访问一个网站时,他们的浏览器需要下载显示页面所需的所有资源。这包括HTML、CSS、JavaScript、图像和其他静态资源。如果浏览器的缓存中已经有某个资源的副本,那么加载该资源的速度要比从服务器下载快得多。
下面是一个简单的图表,展示了如何从服务器请求页面。在这个例子中,用户需要等待一秒多的时间才能加载页面。
不使用缓存时:
下面这张图展示了在不联系服务器的情况下如何从缓存中检索页面。在这个例子中,用户可以立即获取页面:
使用缓存时:
在这些示例中,缓存和浏览器一样,都存在于用户设备上。这就是它如此快速的原因。
举个实际例子,以下是两次测试运行,展示了加载时不使用缓存和使用缓存的情况:
虽然有很多有趣的指标值得关注,但我们还是重点看看"页面权重"这一指标:
- 未使用缓存时:"页面权重"为4.34M
- 使用缓存时:"页面权重"为1.63M
这使得页面权重降低了62%,大大缩短了页面加载时间
浏览器缓存如何提高核心网络指标
核心网络指标是一组衡量网站用户体验的指标,它们会影响你的网站在搜索引擎上的搜索排名
这些指标不仅对于首次访问网站很重要,对于再次访问也同样重要。这正是浏览器缓存发挥作用的地方
当资源缓存在用户浏览器中时,用户后续访问时资源加载速度会更快。这可以改善诸如 FCP 和 LCP 等关键指标。
这里有一个示例展示了缓存对某网站的影响:
虽然最大内容绘制(LCP)得分有所提高,但首次内容绘制(FCP)得分却下降了。这凸显了在实施缓存后对网站进行测试的重要性,因为性能可能会有所不同。不过,总体而言,缓存往往能提升整体网页性能。
缓存的类型
有几种类型的缓存可以存储文件副本:
- 浏览器缓存: 存储在您设备的浏览器里, 这包括这些缓存 Service Worker 缓存, back forward 缓存 以及 Cache Storage API
- CDN缓存:位于 CDN 上的服务, 其部署位置更接近用户。
- Proxy 缓存: 位于用户和网络服务器之间的代理服务器上
- APP缓存: 内置于应用程序本身以存储关键数据
不是所有缓存的速度都一样快。你自己设备上的缓存是最快的选择,因为不存在网络延迟。虽然 CDN 缓存仍然需要网络请求,但它通常比一直追溯到源服务器要快得多
浏览器缓存称为私有缓存,而 CDN 缓存称为公共缓存
为了使浏览器缓存有效工作,还必须配置服务器以发送正确的缓存标头
Cache-Control Header 是如何工作的?
Cache-Control
Header 是告知浏览器如何缓存网站资源的主要工具。没有它,浏览器将使用自身的缓存规则,但是这个可能达不到你的预期
以下是控制缓存行为的一些简单指令:
no-store
:浏览器不会将任何关于这个内容的副本存储在本地,以确保敏感信息(例如密码)不会在用户的设备上被保留private
:这些内容只能被特定用户的浏览器缓存,适用于需要个性化内容的情况max-age=秒数
:这就像是一个保质期 - 最常见的指令public
:允许任何缓存(如CDN和其他中间设备)存储内容,以加快内容传输速度immutable
:适用于那些不会被修改的文件,比如有版本号的资源,确保客户端永远使用的是同一个文件版本no-cache
:客户端在每次使用缓存内容之前都需要向服务器验证该缓存是否仍然有效,以确保客户端拥有最新的内容must-revalidate
:就像是你的食品标签上写着"过期请勿食用"。这个指令告诉浏览器,如果内容已经过期,就不要使用它,而是在使用前先向服务器检查是否有更新的版本。这确保了客户端总是拥有最新的内容s-maxage=秒数
:这就像是给CDN下达一个指令,告诉它要将这个内容保留在缓存中多长时间(以秒为单位),与浏览器缓存分开。这样可以确保CDN在一段时间内提供快速访问,而不必每次都向原始服务器请求内容。stale-while-revalidate
:这个指令允许客户端在获取新版本时,暂时使用旧版本,以提高用户感知的速度。同时,客户端会在后台请求新内容,确保及时更新。
你可结合这些指令为网站上的每个资源创建缓存策略。例如:
arduino
// 都能够将此资源缓存一天,一天过后,需检查其是否出现了更改
Cache-Control: public, max-age=86400, must-revalidate
如何使用Cache-control
的示例
对于动态资源,如 HTML 页面(例如, https://example.com/shop
),首先要做的是:
arduino
// 告知浏览器在显示缓存版本前始终和服务器进行检查。
// 资源会被缓存(节省下载时间),但被视为"立即过期",
// 所以浏览器必须在使用前验证其是否仍然有效。
// 对于经常变动的页面(例如主页或产品页面),这是一种安全的方式
max-age=0,must-revalidate,public
对于静态资源, 例如, https://example.com/main.12345678.css
),如图像、CSS 和 JavaScript,首先使用:
arduino
// 浏览器将缓存该资源一年,且该资源被视为不可变的
// 意味着它永远不会改变
public, max-age=31536000, immutable
缓存的重新验证:检查缓存资源是否过期
当浏览器对您的网站资源进行缓存时,它需要了解何时去获取新版本。此时,缓存重新验证便发挥了作用,
这是一种用于检查缓存内容是否仍然可用的明智手段。
实际场景:
假设你的浏览器缓存了一个资源,其缓存策略为max-age=10,must-revalidate,public
。10秒后,这个资源就会"过期"。但浏览器并不需要立即重新下载该资源,它只需询问服务器:"这个资源有更新吗?"
这产生了三种可能的情况:
- 缓存没有过期 (在 max-age 内):
- 浏览器直接使用缓存版本
- 无需与服务器通信
- 加载速度极快
- 资源已经过期了, 但是新的内容跟老的内容相比, 没有任何更新:
- 浏览器询问服务器内容是否有更改
- 服务器回复"304 未修改"(响应信息)
- 浏览器继续使用缓存版本
- 这比重新下载要快得多
- 已更改的陈旧资源:
- 浏览器询问服务器内容是否有更改
- 服务器发送资源的新版本
- 浏览器更新其缓存
讲一个真实场景的例子
首次访问页面:
- 服务器发送:
Cache-Control: public, max-age=10
- 服务器发送 1MB 数据
- 浏览器将其保存在缓存中
第二次访问(10秒内):
- 浏览器认为:这个仍然是最新的!
- 浏览器使用缓存版本
- 完全无需网络请求!
第三次访问(10 秒后):
- 浏览器认为:这可能已经过时了
- 浏览器询问服务器:自从我上次获取后,这个有变化吗?
- 浏览器发送:
If-Modified-Since: 01 March 2025 18:30:00 GMT
和If-None-Match: W/"164-198173818fc"
- 服务器回复:
304 未修改
- 仅传输了少量字节,而非再次传输 1MB!
除了max-age, 我们也可以用 ETag 来重新验证, ETag 就像内容的指纹 - 如果内容发生轻微变化,ETag 也会发生变化。浏览器可以将 ETag 发送到服务器,以检查其缓存版本是否与当前版本匹配。
在 JavaScript 的 fetch 中使用缓存设置
在JavaScript中发出获取请求时,你可以控制缓存行为:
php
// 这告诉浏览器在使用缓存内容之前始终先与服务器进行核对
fetch("/api/data", {
cache: "no-cache"
});
缓存清除:如何清除资源的缓存
缓存清除是一种用于确保浏览器下载文件最新版本的技术,即使该文件已被缓存很长时间。
想象一下,你有一个带有缓存头(如max-age=31536000
)的 CSS 文件,该缓存告诉浏览器将该文件缓存一年。如果你更新了 CSS 文件,浏览器在缓存过期之前不会获取新版本,这个时候就需要缓存清除
一种常见的缓存清除方法是使用带版本号的文件名。不要将文件命名为main.css
,而是可以将其命名为main.12345678.css
。版本号通常是文件内容的哈希值,所以每当文件发生变化时,哈希值也会改变。这样,浏览器就会将其识别为一个新文件并下载它
importmap 与 缓存清除 兼容吗?
Import maps 与缓存清除配合得很好。它们允许你定义模块说明符和URL之间的映射,这可用于在一处引用带版本号的文件。
然后,你的其他模块可以导入该模块说明符,浏览器将自动获取正确的带版本号的文件:
HTML (index.html):
xml
<script type="importmap">
{
"imports": {
"utils": "https://cdn.com/utils.12345678.js"
}
}
</script>
模块导入映射
- 允许您在代码中使用自定义的模块导入路径,而不是直接在代码中写入完整的 URL。
- 在这种情况下,通过将
utils
模块映射到https://cdn.com/utils.12345678.js
,您可以在代码中使用import 'utils'
来导入这个模块,而不必每次都写完整的 URL。- 这样做的好处是,如果您需要更改
utils
模块的来源,只需更新导入映射,而无需修改代码中的每个模块导入语句
JavaScript (main.123.js):
css
//无需从" https://cdn.com/utils.12345678.js "导入 * 作为utils; 导入 作为 utils 从 "utils";
console.log(utils);
这种方法效果很好,并且缓解了如果main.123.js
的内容更新为引用utils
的新版本时会出现的失效链问题。因为在这种情况下,浏览器需要再次下载两个 文件。而使用导入映射,浏览器只会下载更新的文件(utils
)。
使用高效的缓存策略提供静态资源
以高效的缓存策略提供静态资源服务是一项 Lighthouse 审核项 ,用于验证是否对可合理缓存的资源进行了缓存。
你可以用一些合适的 max-age
指令来提高 Lighthouse 审核的分数
如何在开发者工具中禁用缓存
所有现代浏览器的开发者工具都会显示一些缓存信息。本节重点介绍Chrome 开发者工具
- 在 Chrome 开发者工具中(其他浏览器的开发者工具也会有类似设置),确保 Disable cache 未勾选
这将确保浏览器缓存的使用情况与实际场景一致。
- 然后,导航到某个页面,并检查网络面板。
- 重新加载页面 - 只需点击重新加载图标,避免执行可能绕过缓存的强制重新加载。
- 再次观察网络面板。"大小"列对于缓存资源应显示"磁盘缓存"或类似内容。
在网络面板中点击网络资源会显示HTTP请求和响应头。不过,为方便起见,你可以将DevTools配置为始终在整个网络面板中显示与缓存相关的头信息:
- 右键点击网络面板的列标题,然后找到"响应头"。
- 勾选"etag"、"cache-control"和"last-modified"选项。
在 Nginx Web 服务器上启用缓存
以下是在Nginx上启用缓存的方法:
arduino
location / {
add_header Cache-Control "max-age=0, must-revalidate, public";
}
location /static/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}