早上好,各位缓存爱好者!今天咱们来聊聊浏览器缓存这回事儿,保证让你们听完以后,对强缓存和协商缓存这对好兄弟了如指掌,还能把 JavaScript 资源缓存优化玩得飞起。准备好了吗?咱们这就开讲!
一、缓存,浏览器里的“小金库”
想象一下,你每次访问一个网站都要重新下载所有东西,这得等到猴年马月啊!浏览器缓存就是为了解决这个问题而生的。它就像浏览器里的小金库,把一部分资源(比如图片、CSS、JavaScript)存起来,下次再访问同一个网站的时候,直接从金库里拿,速度那叫一个嗖嗖的!
二、强缓存:我的地盘我做主
强缓存就像一个霸道的总裁,资源一旦进了它的地盘,在有效期内,浏览器连服务器都不问一声,直接从缓存里拿。
1. 控制强缓存的“圣旨”:Cache-Control
和 Expires
强缓存主要通过 HTTP 响应头里的 Cache-Control
和 Expires
来控制。
-
Cache-Control
:现代浏览器的首选Cache-Control
是一个更强大的指令,它允许你更细粒度地控制缓存行为。max-age=<seconds>
:指定资源被缓存的最大时间(单位是秒)。例如,Cache-Control: max-age=3600
表示资源可以被缓存 3600 秒(1 小时)。s-maxage=<seconds>
:类似于max-age
,但它只适用于共享缓存(例如 CDN)。private
:表示资源只能被用户私有缓存(例如,用户的浏览器),不能被共享缓存。public
:表示资源可以被任何缓存缓存,包括共享缓存。no-cache
:表示资源可以被缓存,但每次使用缓存时都需要与服务器进行协商(协商缓存)。no-store
:表示资源根本不应该被缓存。
例子:
HTTP/1.1 200 OK Cache-Control: max-age=604800, public
这段响应头告诉浏览器,这个资源可以被缓存 604800 秒(7 天),并且可以被任何缓存(包括 CDN)缓存。
-
Expires
:老古董,但依然有用Expires
指定资源过期的时间点(绝对时间)。例子:
HTTP/1.1 200 OK Expires: Wed, 21 Oct 2015 07:28:00 GMT
这段响应头告诉浏览器,这个资源在 2015 年 10 月 21 日 07:28:00 GMT 之后就过期了。
注意:
Expires
使用的是绝对时间,所以需要保证客户端和服务端的时间同步,否则可能会出现缓存失效的问题。Cache-Control
使用的是相对时间,所以更可靠,也更推荐使用。
2. 强缓存的流程
- 浏览器发起请求。
- 浏览器检查本地缓存,如果找到该资源,并且资源未过期(根据
Cache-Control
或Expires
判断),则直接从缓存中获取资源,不再向服务器发送请求,返回 200 OK (from cache)。 - 如果资源过期或者没有找到,则进入协商缓存阶段。
三、协商缓存:问问服务器,我还能用吗?
协商缓存不像强缓存那么霸道,它会先向服务器确认一下,缓存的资源是否仍然有效。
1. 控制协商缓存的“令牌”:Last-Modified
/If-Modified-Since
和 ETag
/If-None-Match
-
Last-Modified
/If-Modified-Since
:基于时间戳-
Last-Modified
:服务器在响应头中告诉浏览器,资源的最后修改时间。例子:
HTTP/1.1 200 OK Last-Modified: Tue, 15 Nov 2023 12:00:00 GMT
-
If-Modified-Since
:浏览器在下次请求时,会将Last-Modified
的值放到请求头If-Modified-Since
中,询问服务器资源是否修改过。如果资源没有修改,服务器返回 304 Not Modified,浏览器继续使用缓存;如果资源修改了,服务器返回新的资源和 200 OK。例子:
GET /style.css HTTP/1.1 If-Modified-Since: Tue, 15 Nov 2023 12:00:00 GMT
-
-
ETag
/If-None-Match
:基于内容指纹-
ETag
:服务器在响应头中告诉浏览器,资源的唯一标识(内容指纹)。ETag
通常是对资源内容进行哈希计算得到的值。例子:
HTTP/1.1 200 OK ETag: "6373-5be6d5b54e1cc"
-
If-None-Match
:浏览器在下次请求时,会将ETag
的值放到请求头If-None-Match
中,询问服务器资源是否修改过。如果资源没有修改,服务器返回 304 Not Modified,浏览器继续使用缓存;如果资源修改了,服务器返回新的资源和 200 OK。例子:
GET /script.js HTTP/1.1 If-None-Match: "6373-5be6d5b54e1cc"
-
2. 协商缓存的流程
- 浏览器发起请求。
- 浏览器检查本地缓存,如果找到该资源,并且资源已过期,则进入协商缓存阶段。
- 浏览器将上次响应头中的
Last-Modified
值放到请求头If-Modified-Since
中,或者将ETag
值放到请求头If-None-Match
中,发送给服务器。 - 服务器收到请求后,进行判断:
- 如果资源未修改(
Last-Modified
值与服务器上的资源最后修改时间一致,或者ETag
值与服务器上的资源ETag
值一致),则返回 304 Not Modified,告诉浏览器继续使用缓存。 - 如果资源已修改,则返回新的资源和 200 OK。
- 如果资源未修改(
- 浏览器收到 304 Not Modified,则从缓存中获取资源;收到 200 OK,则更新缓存。
3. ETag
优于 Last-Modified
的原因
- 精度更高:
Last-Modified
只能精确到秒级,如果资源在一秒内多次修改,Last-Modified
就无法区分。ETag
可以更精确地标识资源的变化。 - 更灵活: 有些服务器可能无法精确获取资源的最后修改时间,或者某些资源的修改时间可能不准确。
ETag
可以通过对资源内容进行哈希计算来解决这个问题。 - 解决弱一致性问题: 即使资源内容没有发生变化,
Last-Modified
也可能因为某些原因(例如,服务器上的文件系统发生了变化)而发生变化。ETag
可以通过比较资源内容的哈希值来解决这个问题。
四、JavaScript 资源缓存优化实战
JavaScript 资源是网站性能的关键因素之一,所以对 JavaScript 资源进行缓存优化非常重要。
1. 版本号控制:让缓存“焕然一新”
最常用的方法是在 JavaScript 文件名中添加版本号。当 JavaScript 文件发生变化时,更新版本号,浏览器就会重新下载新的 JavaScript 文件。
例子:
- 原始 JavaScript 文件名:
app.js
- 添加版本号后的 JavaScript 文件名:
app.v1.0.0.js
实现方法:
- 手动更新: 在每次发布新版本时,手动更新 HTML 文件中的 JavaScript 文件名。
-
构建工具: 使用构建工具(例如 Webpack、Parcel、Rollup)自动生成带有哈希值的 JavaScript 文件名。
Webpack 示例:
// webpack.config.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.[contenthash].js', // 使用 contenthash 生成哈希值 path: path.resolve(__dirname, 'dist'), clean: true, // 清理 dist 目录 }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', filename: 'index.html', }), ], mode: 'production', };
在这个 Webpack 配置中,
output.filename
使用了[contenthash]
,Webpack 会根据 JavaScript 文件内容生成哈希值,并将哈希值添加到文件名中。当 JavaScript 文件内容发生变化时,哈希值也会发生变化,浏览器就会重新下载新的 JavaScript 文件。
2. 合理设置 Cache-Control
:让缓存“活得更久”
对于不经常变化的 JavaScript 资源,可以设置较长的 max-age
,让浏览器尽可能长时间地缓存这些资源。
例子:
HTTP/1.1 200 OK
Cache-Control: max-age=31536000, public // 缓存一年
对于经常变化的 JavaScript 资源,可以设置 no-cache
,让浏览器每次使用缓存时都与服务器进行协商。
例子:
HTTP/1.1 200 OK
Cache-Control: no-cache
3. 使用 CDN:让缓存“无处不在”
CDN(内容分发网络)可以将 JavaScript 资源缓存到全球各地的服务器上,用户可以从离自己最近的服务器上获取资源,从而提高访问速度。
使用方法:
- 将 JavaScript 资源上传到 CDN。
- 在 HTML 文件中使用 CDN 提供的 URL 引用 JavaScript 资源。
4. 代码分割:让缓存“更精细”
将 JavaScript 代码分割成多个小文件,可以更好地利用缓存。例如,可以将第三方库的代码单独打包成一个文件,因为第三方库的代码通常不会频繁变化。
Webpack 示例:
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js',
vendor: ['lodash'], // 将 lodash 单独打包成一个文件
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
}),
],
mode: 'production',
};
在这个 Webpack 配置中,optimization.splitChunks
用于代码分割,cacheGroups.vendor
将 node_modules
目录下的代码单独打包成一个名为 vendors
的文件。
5. 懒加载:让缓存“按需加载”
懒加载是指在需要使用 JavaScript 代码时才加载,而不是在页面加载时就加载所有代码。这可以减少初始加载时间,提高页面性能。
实现方法:
Intersection Observer API
: 使用Intersection Observer API
监听元素是否进入视口,当元素进入视口时才加载 JavaScript 代码。-
动态
import()
: 使用import()
函数动态加载 JavaScript 代码。例子:
// index.js const button = document.getElementById('myButton'); button.addEventListener('click', async () => { const module = await import('./myModule.js'); // 动态加载 myModule.js module.default(); // 调用 myModule.js 中的默认导出函数 }); // myModule.js export default function() { console.log('Hello from myModule.js!'); }
五、缓存策略选择:强缓存 vs 协商缓存
特性 | 强缓存 | 协商缓存 |
---|---|---|
是否与服务器通信 | 否 | 是 |
控制方式 | Cache-Control 和 Expires |
Last-Modified /If-Modified-Since 和 ETag /If-None-Match |
适用场景 | 静态资源(例如,图片、CSS、JavaScript),这些资源在一定时间内不会发生变化。 | 动态资源,或者需要验证资源是否已更新的静态资源。 |
优点 | 速度快,不需要与服务器通信,可以减少服务器压力。 | 可以保证资源是最新的,即使资源发生了变化,浏览器也会及时更新。 |
缺点 | 如果资源发生了变化,浏览器可能无法及时更新,需要通过版本号控制或者手动清除缓存来解决。 | 速度相对较慢,需要与服务器通信,会增加服务器压力。 |
策略选择 | 1. 优先使用强缓存: 对于静态资源,尽可能使用强缓存,并设置较长的 max-age 。2. 结合使用协商缓存: 对于动态资源,或者需要验证资源是否已更新的静态资源,可以使用协商缓存。3. 版本号控制: 对于经常变化的静态资源,可以使用版本号控制来强制浏览器更新缓存。4. CDN: 使用 CDN 可以将资源缓存到全球各地的服务器上,提高访问速度。5. 代码分割: 将 JavaScript 代码分割成多个小文件,可以更好地利用缓存。6. 懒加载: 使用懒加载可以减少初始加载时间,提高页面性能。 |
1. 优先使用强缓存: 对于静态资源,尽可能使用强缓存,并设置较长的 max-age 。2. 结合使用协商缓存: 对于动态资源,或者需要验证资源是否已更新的静态资源,可以使用协商缓存。3. 版本号控制: 对于经常变化的静态资源,可以使用版本号控制来强制浏览器更新缓存。4. CDN: 使用 CDN 可以将资源缓存到全球各地的服务器上,提高访问速度。5. 代码分割: 将 JavaScript 代码分割成多个小文件,可以更好地利用缓存。6. 懒加载: 使用懒加载可以减少初始加载时间,提高页面性能。 |
六、总结
浏览器缓存是提高网站性能的重要手段。通过合理使用强缓存和协商缓存,可以减少服务器压力,提高用户体验。对于 JavaScript 资源,可以使用版本号控制、合理设置 Cache-Control
、使用 CDN、代码分割和懒加载等方法进行优化。
好了,今天的讲座就到这里。希望大家能够灵活运用这些缓存策略,让你的网站飞起来!如果还有什么疑问,欢迎随时提问,我将竭诚为大家解答。下次再见!