0%

理解浏览器缓存

简介

发布一个新版本页面后,如果未加干预,用户往往还会看到更新前的样式,这就是浏览器端的缓存在“捣乱”了,本文写于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
2
3
4
output: {
// 这样配置,webpack会为打包出的文件加上8位hash
filename: 'js/[name].[contenthash:8].js'
}

nginx配置干预缓存

nginx可以用add_header增加响应头来控制缓存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 80 default;
server_name {{NGINX_SERVER_NAME}};
root /w/;
index index.html;
location @rewrite {
rewrite ^/app/(.*)$ /$1;
}

location /app {
try_files $uri $uri/ /app/index.html;
add_header Cache-Control "no-cache, no-store";
}
}

控制index.html的缓存

index.html可以配置meta标签来控制缓存:

1
2
3
4
<head>
<!-- 关闭index.html的缓存 -->
<meta http-equiv="Cache-control" content="no-cache">
<head>

拼接查询参数丢弃缓存

script标签中的src,link标签中的href,都可以在路径后面拼上 ?xxx,拼接参数不会影响资源访问,但可以起到改变url丢弃缓存的目的。

用户如何丢弃缓存

如果你是一个用户,可以用以下任意一种方法来丢弃缓存:

  1. 在chrome中按 ctrl+F5 强制刷新
  2. 打开 dev-tool 工具,鼠标左键长按浏览器的刷新按钮,选择 清空缓存并进行硬刷新
  3. 在浏览器设置中清除缓存
天真小兮兮 wechat
扫描二维码关注我的订阅号“前端小海螺”