简介
发布一个新版本页面后,如果未加干预,用户往往还会看到更新前的样式,这就是浏览器端的缓存在“捣乱”了,本文写于2020年3月,介绍浏览器的缓存机制,还会告诉你如何在需要的时候使用或丢弃缓存。
什么是浏览器缓存
我们知道,前端代码100%地运行在浏览器端,当用户打开一个页面时,需要下载 html, js, css 和一堆图片,页面在短期内一般不会有改变,通常,浏览器会聪明地记录这些静态资源的相关信息,保存在临时目录中的css和图片等等,都不会被删除,这就是缓存的一种。当用户再次访问同样的页面时,浏览器会优先使用缓存,找不到缓存时,才会从互联网重新下载。此时虽然页面的样式已经在服务端更新了,但浏览器认为本地已经有了这份样式,就不会再重新下载新样式,用户看到的就是老版本的页面了。
- 浏览器缓存(Browser Caching)是为了节约网络的资源加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面的阅览。
- 浏览器缓存能够显著加快页面加载速度,节约流量,也能减轻服务端的访问压力。
缓存的方式
要想更好地利用缓存,我们需要知道缓存的工作方式。
缓存是在服务器响应浏览器发出的请求时建立的,每次浏览器发送请求时,都会先尝试寻找缓存,拿到服务器发送的响应后,再根据响应报文中的缓存标识来决定是否要建立缓存。
浏览器的缓存分为两种:强缓存和协商缓存
强缓存
强缓存就是指,浏览器不向服务端发送请求,直接从缓存读取信息,这时对应的请求会返回状态码200 。
强缓存由 Expires(HTTP/1) 和 Cache-Control(HTTP/1.1) 这两种Header控制。
Expires
这个字段是一个GMT格式的字符串,比如Mon, Mar 09 2020 23:11:29 GMT,如果发送请求时,时间(本地时间)超过了这个时间,那么缓存就会失效,浏览器会到服务器去获取资源,否则缓存有效,返回状态码304 。
Cache-Control
这个字段在HTTP/1.1中生效,这也是浏览器默认的协议版本,它可以在请求头或响应头中配置。如果 Cache-Control 与 Expires 同时存在,那么 Cache-Control 的优先级更高,只有 Cache-Control 会生效
当 Cache-Control: max-age=300
时,也就意味着缓存内容将在300秒后失效。
Cache-Control经常用到这些值:
- private 默认值,表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。私有缓存可以缓存响应内容
- public 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容(例如,该响应没有max-age指令或Expires消息头)
- no-cache 资源会被缓存,但所有的请求都要发到服务端,经过服务端验证来决定是否有效(协商缓存)
- no-store 所有资源都不会被缓存
- max-age=number 缓存在一定时期内有效,max-age的取值不能超过31536000(一年)
关于 Cache-Control, 更详细的内容可以看 MDN
协商缓存
强缓存只依赖本地的信息来判断缓存是否有效,有时我们需要由服务端来决定是否使用缓存,这时候就要用到协商缓存。
协商缓存主要由 Last-Modified / If-Modified-Since 和 Etag / If-None-Match 字段控制。
Last-Modified / If-Modified-Since
服务端响应请求的时候,会用 Last-Modified 标记资源最后一次被修改的时间,例如: last-modifiled: Mon, Mar 09 2020 23:11:29 GMT
,
客户端再次发送请求时,会用上一次返回的 Last-Modified 的值作为 If-Modified-Since,服务端接收到请求后,如果发现请求头中含有 If-Modified-Since,则会把这两个值对比,如果服务器上资源的修改时间大于 If-Modified-Since,则返回新的资源,状态码为200,否则返回状态码304,通知客户端使用缓存 。
如果没有命中缓存,那么响应中的 Last-Modified 会被更新,反之则不更新。
Etag / If-None-Match
Etag 表示资源文件的唯一标志,由服务端在响应请求时生成,例如: etag: "6e6-577d9f8d6f980"
,
客户端再次发送请求时,用上次返回的 Etag 作为 If-None-Match 带在请求头中,服务端接收到请求后,如果发现请求头中含有 If-None-Match,则会把 If-None-Match 与资源文件的Etag值做对比,如果相同,则返回304,使用缓存,否则返回新资源,状态码为200 。
无论是否命中缓存,响应中都会返回 Etag,如果命中了缓存,则返回的 Etag 与上次相同。
如果请求头中同时含有 If-None-Match 和 If-Modified-Since,则仅有 If-None-Match 生效。
这张图大致描述了浏览器选择缓存的流程:
如何判断是否使用了缓存
打开浏览器的开发者工具,可以直观地观察缓存的启用,这里以chrome的dev-tool为例。
从 status 和 size 可以看出,这些资源命中了缓存,memory cache是指缓存保存在内存中,关闭浏览器时缓存就释放掉了,一般脚本,图片是这种方式缓存的,还有一种存储方式是 disk cache,说明缓存保存在硬盘中,关闭浏览器后缓存不会释放,一般css会用这种方式缓存:
vip这个资源状态码为304,由于有 if-modified-since,使用了协商缓存:
jquery这个资源,状态码为200,由于有 cache-control,使用了强缓存:
如何合理利用浏览器缓存
针对频繁变动的资源
在请求头中带上 Cache-Control: no-cache
来避免频繁变动的资源命中缓存。
针对几乎不会变的资源
配置 Cache-Control: max-age=31536000
来强制使用缓存,一般jquery等第三方库引入时,可以用这个方法,如果要更新版本,直接更新url即可,例如 jquery-1.9.0.min.js
。
webpack打包时丢弃旧版本缓存
发布新版本后,我们系统客户端能及时更新,如果请求的url不同,则不会命中缓存。可以在webpack配置中这样写:
1 | output: { |
nginx配置干预缓存
nginx可以用add_header
增加响应头来控制缓存:
1 | server { |
控制index.html的缓存
index.html可以配置meta标签来控制缓存:
1 | <head> |
拼接查询参数丢弃缓存
script标签中的src,link标签中的href,都可以在路径后面拼上 ?xxx
,拼接参数不会影响资源访问,但可以起到改变url丢弃缓存的目的。
用户如何丢弃缓存
如果你是一个用户,可以用以下任意一种方法来丢弃缓存:
- 在chrome中按
ctrl+F5
强制刷新 - 打开 dev-tool 工具,鼠标左键长按浏览器的刷新按钮,选择
清空缓存并进行硬刷新
- 在浏览器设置中清除缓存