浏览器缓存的全过程:
很多网站的资源后面都加了版本号,这样做的目的是:每次升级了 JS 或 CSS 文件后,为了防止浏览器进行缓存,强制改变版本号,客户端浏览器就会重新下载新的 JS 或 CSS 文件 ,以保证用户能够及时获得网站的最新更新。
使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。
强缓存策略可以通过两种方式来设置,分别是 http 头信息中的 Expires 属性和 Cache-Control 属性。
(1)服务器通过在响应头中添加 Expires 属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求。这个时间是一个绝对时间,它是服务器的时间,因此可能存在这样的问题,就是客户端的时间和服务器端的时间不一致,或者用户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的结果。
(2)Expires 是 http1.0 中的方式,因为它的一些缺点,在 HTTP 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,它提供了对资源的缓存的更精确的控制。它有很多不同的值,
Cache-Control
可设置的字段:
public
:设置了该字段值的资源表示可以被任何对象(包括:发送请求的客户端、代理服务器等等)缓存。这个字段值不常用,一般还是使用max-age=来精确控制;private
:设置了该字段值的资源只能被用户浏览器缓存,不允许任何代理服务器缓存。在实际开发当中,对于一些含有用户信息的HTML,通常都要设置这个字段值,避免代理服务器(CDN)缓存;no-cache
:设置了该字段需要先和服务端确认返回的资源是否发生了变化,如果资源未发生变化,则直接使用缓存好的资源;no-store
:设置了该字段表示禁止任何缓存,每次都会向服务端发起新的请求,拉取最新的资源;max-age=
:设置缓存的最大有效期,单位为秒;s-maxage=
:优先级高于max-age=,仅适用于共享缓存(CDN),优先级高于max-age或者Expires头;max-stale[=]
:设置了该字段表明客户端愿意接收已经过期的资源,但是不能超过给定的时间限制。一般来说只需要设置其中一种方式就可以实现强缓存策略,当两种方式一起使用时,Cache-Control 的优先级要高于 Expires。
no-cache和no-store很容易混淆:
如果命中强制缓存,我们无需发起新的请求,直接使用缓存内容,如果没有命中强制缓存,如果设置了协商缓存,这个时候协商缓存就会发挥作用了。
上面已经说到了,命中协商缓存的条件有两个:
max-age=xxx
过期了no-store
使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本。如果资源发生了修改,则返回修改后的资源。
协商缓存也可以通过两种方式来设置,分别是 http 头信息中的** Etag** 和** Last-Modified **属性。
(1)服务器通过在响应头中添加 Last-Modified 属性来指出资源最后一次修改的时间,当浏览器下一次发起请求时,会在请求头中添加一个 If-Modified-Since 的属性,属性值为上一次资源返回时的 Last-Modified 的值。当请求发送到服务器后服务器会通过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么返回 304 状态,让客户端使用本地的缓存。如果资源已经被修改了,则返回修改后的资源。使用这种方法有一个缺点,就是 Last-Modified 标注的最后修改时间只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,那么文件已将改变了但是 Last-Modified 却没有改变,这样会造成缓存命中的不准确。
(2)因为 Last-Modified 的这种可能发生的不准确性,http 中提供了另外一种方式,那就是 Etag 属性。服务器在返回资源的时候,在头信息中添加了 Etag 属性,这个属性是资源生成的唯一标识符,当资源发生改变的时候,这个值也会发生改变。在下一次资源请求时,浏览器会在请求头中添加一个 If-None-Match 属性,这个属性的值就是上次返回的资源的 Etag 的值。服务接收到请求后会根据这个值来和资源当前的 Etag 的值来进行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比 Last-Modified 的方式更加精确。
当 Last-Modified 和 Etag 属性同时出现的时候,Etag 的优先级更高。使用协商缓存的时候,服务器需要考虑负载平衡的问题,因此多个服务器上资源的 Last-Modified 应该保持一致,因为每个服务器上 Etag 的值都不一样,因此在考虑负载平衡时,最好不要设置 Etag 属性。
总结:
强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。
对于浏览器的缓存,主要针对的是前端的静态资源,最好的效果就是,在发起请求之后,拉取相应的静态资源,并保存在本地。如果服务器的静态资源没有更新,那么在下次请求的时候,就直接从本地读取即可,如果服务器的静态资源已经更新,那么我们再次请求的时候,就到服务器拉取新的资源,并保存在本地。这样就大大的减少了请求的次数,提高了网站的性能。这就要用到浏览器的缓存策略了。
所谓的浏览器缓存指的是浏览器将用户请求过的静态资源,存储到电脑本地磁盘中,当浏览器再次访问时,就可以直接从本地加载,不需要再去服务端请求了。
使用浏览器缓存,有以下优点:
强缓存(Strong Caching) 是浏览器缓存机制中的一种,旨在通过设置特定的HTTP头来告诉浏览器在一定时间内直接使用缓存的资源,而无需向服务器发起请求。强缓存可以显著提高页面加载速度和性能。以下是关于强缓存的详细说明:
Cache-Control
Expires
Cache-Control
Cache-Control
是一个通用的缓存控制头,可以设置多种缓存策略。对于强缓存,常用的指令包括:
max-age=
:
Cache-Control: max-age=3600
表示资源在缓存中有效时间为1小时。s-maxage=
:
max-age
,但仅适用于共享缓存(如CDN)。Cache-Control: s-maxage=3600
表示共享缓存中资源的有效时间为1小时。public
:
Cache-Control: public, max-age=3600
。private
:
Cache-Control: private, max-age=3600
。Expires
Expires
头指定资源的过期日期和时间,是一个绝对时间点。
Expires
头的值是一个HTTP日期时间字符串。Expires: Wed, 21 Oct 2020 07:28:00 GMT
。Cache-Control
或 Expires
)。Cache-Control
假设服务器返回以下响应头:
HTTP/1.1 200 OK
Cache-Control: max-age=3600
Content-Type: image/png
Content-Length: 12345
... (image data)
Cache-Control: max-age=3600
。Expires
假设服务器返回以下响应头:
HTTP/1.1 200 OK
Expires: Wed, 21 Oct 2020 07:28:00 GMT
Content-Type: image/png
Content-Length: 12345
... (image data)
Expires: Wed, 21 Oct 2020 07:28:00 GMT
。Wed, 21 Oct 2020 07:28:00 GMT
。Wed, 21 Oct 2020 07:28:00 GMT
之前再次请求该资源,浏览器会直接使用缓存的资源,不会向服务器发起请求。Wed, 21 Oct 2020 07:28:00 GMT
再次请求该资源,浏览器会向服务器发起请求以获取最新资源。Cache-Control
或 Expires
设置不当,可能导致缓存过期时间过长或过短,影响用户体验和性能。Cache-Control
和 Expires
为了更好地控制缓存行为,可以同时使用 Cache-Control
和 Expires
头。Cache-Control
提供更灵活的控制选项,而 Expires
提供绝对时间点。
HTTP/1.1 200 OK
Cache-Control: max-age=3600, public
Expires: Wed, 21 Oct 2020 07:28:00 GMT
Content-Type: image/png
Content-Length: 12345
... (image data)
Cache-Control: max-age=3600, public
: 资源在缓存中有效时间为1小时,允许任何缓存存储资源。Expires: Wed, 21 Oct 2020 07:28:00 GMT
: 资源的过期时间为 Wed, 21 Oct 2020 07:28:00 GMT
。Cache-Control
和 Expires
头控制资源的缓存行为。Cache-Control
: 提供灵活的缓存控制选项,如 max-age
和 s-maxage
。Expires
: 指定资源的绝对过期时间。协商缓存(Conditional Caching) 是浏览器缓存机制中的一种,用于在缓存资源过期或不确定是否为最新版本时,与服务器进行协商以确定是否可以使用缓存的资源。协商缓存通过设置特定的HTTP头来实现,确保浏览器在使用缓存资源之前验证资源的最新性。以下是关于协商缓存的详细说明:
ETag
和 If-None-Match
Last-Modified
和 If-Modified-Since
ETag
和 If-None-Match
ETag
** (Entity Tag)**:
ETag: "123456789"
。If-None-Match
:
ETag
值包含在 If-None-Match
头中,请求服务器验证资源是否为最新版本。If-None-Match: "123456789"
。Last-Modified
和 If-Modified-Since
Last-Modified
:
Last-Modified: Wed, 21 Oct 2020 07:28:00 GMT
。If-Modified-Since
:
Last-Modified
时间戳包含在 If-Modified-Since
头中,请求服务器验证资源是否为最新版本。If-Modified-Since: Wed, 21 Oct 2020 07:28:00 GMT
。ETag
或 Last-Modified
)。If-None-Match
或 If-Modified-Since
),向服务器发起验证请求。304 Not Modified
状态码,表示缓存的资源是最新的。ETag
和 If-None-Match
GET /resource HTTP/1.1
Host: example.com
HTTP/1.1 200 OK
ETag: "123456789"
Content-Type: image/png
Content-Length: 12345
... (image data)
GET /resource HTTP/1.1
Host: example.com
If-None-Match: "123456789"
HTTP/1.1 304 Not Modified
- 如果资源已被修改:
HTTP/1.1 200 OK
ETag: "987654321"
Content-Type: image/png
Content-Length: 12345
... (new image data)
Last-Modified
和 If-Modified-Since
GET /resource HTTP/1.1
Host: example.com
HTTP/1.1 200 OK
Last-Modified: Wed, 21 Oct 2020 07:28:00 GMT
Content-Type: image/png
Content-Length: 12345
... (image data)
GET /resource HTTP/1.1
Host: example.com
If-Modified-Since: Wed, 21 Oct 2020 07:28:00 GMT
HTTP/1.1 304 Not Modified
- 如果资源已被修改:
HTTP/1.1 200 OK
Last-Modified: Thu, 22 Oct 2020 08:28:00 GMT
Content-Type: image/png
Content-Length: 12345
... (new image data)
304 Not Modified
状态码,不传输资源内容,节省带宽。ETag
和 Last-Modified
头,增加了服务器的复杂性。Last-Modified
头的时间戳精度有限,可能无法精确反映资源的修改时间。为了更好地管理缓存,可以结合使用强缓存和协商缓存。强缓存用于在缓存未过期时直接使用缓存的资源,协商缓存用于在缓存过期时验证资源的最新性。
HTTP/1.1 200 OK
Cache-Control: max-age=3600, public
ETag: "123456789"
Content-Type: image/png
Content-Length: 12345
... (image data)
Cache-Control: max-age=3600, public
: 资源在缓存中有效时间为1小时,允许任何缓存存储资源。ETag: "123456789"
: 资源的唯一标识符。ETag
和 If-None-Match
或 Last-Modified
和 If-Modified-Since
头来验证资源的最新性。ETag
** 和 **If-None-Match
: 使用唯一标识符来验证资源。Last-Modified
** 和 **If-Modified-Since
: 使用最后修改时间来验证资源。设置协商缓存和强缓存通常在后端进行。前端(客户端)主要是接收和处理这些缓存控制头,而不会直接设置这些头。后端通过在HTTP响应头中添加适当的缓存控制信息来指示浏览器如何缓存资源。
强缓存通过 Cache-Control
和 Expires
头来设置。以下是如何在不同后端框架中设置这些头的示例。
const express = require('express');
const app = express();
const path = require('path');
app.use(express.static(path.join(__dirname, 'public'), {
setHeaders: (res, path, stat) => {
res.set('Cache-Control', 'public, max-age=3600'); // 1 hour
res.set('Expires', new Date(Date.now() + 3600 * 1000).toUTCString());
}
}));
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});
协商缓存通过 ETag
和 Last-Modified
头来设置。以下是如何在不同后端框架中设置这些头的示例。
const express = require('express');
const app = express();
const fs = require('fs');
const path = require('path');
app.get('/resource', (req, res) => {
const filePath = path.join(__dirname, 'public', 'resource.png');
const stat = fs.statSync(filePath);
const lastModified = stat.mtime.toUTCString();
const etag = `"${stat.size}-${stat.mtime.getTime()}"`;
if (req.headers['if-none-match'] === etag || req.headers['if-modified-since'] === lastModified) {
return res.status(304).send();
}
res.set('ETag', etag);
res.set('Last-Modified', lastModified);
res.sendFile(filePath);
});
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});
Cache-Control
和 Expires
头设置,指示浏览器在缓存未过期时直接使用缓存的资源。ETag
和 If-None-Match
或 Last-Modified
和 If-Modified-Since
头设置,指示浏览器在缓存过期时与服务器协商资源的最新性。