浏览器的缓存机制
浏览器缓存是前端的老友,常见的是发版后,测试却发现没有变化,使用 ctr + F5 强制刷新之后、或清除浏览器缓存,才能正常展示
对于浏览器缓存,前端对它是又爱又恨,有时想保留,有时想禁掉,所以其机制是究竟为何
什么是浏览器缓存
浏览器缓存就是浏览器根据 url 第一次访问网站之后,将网站的 html、css、js、图片等文件复制一份保留到浏览器中,当你二次访问这个 url 的网站时,如果网站没有明确表示有更新时,浏览器直接在缓存中查找内容,不会再次请求网页内容,只有网页明确表示有更新时,浏览器才会向服务器发起网路请求,再次下载网页。
浏览器缓存优点
- 减少网络带宽消耗
对于网站运营者或者访问网页的用户,带宽代表着成本 ,使用浏览器缓存可以减少网络流量,从而降低运营成本。
- 降低服务器压力
使用浏览器缓存之后,除第一次访问需要向服务器请求网站全部资源,后续访问可以重复使用浏览器本地缓存,减少对服务器的请求,间接降低服务器的压力,同时,搜索引擎的爬虫也会根据缓存过期机制降低抓取的频率,也可以降低服务器压力。
- 减少网络延迟,加快网页加载
浏览器缓存 web 资源后,减少网络请求,可以更快速地获取到服务器返回数据,同时使用浏览器缓存内的文件比服务器获取快很多,所以网页加载速度明显快很多。
浏览器的缓存规则
首先,基本的缓存类型包括强缓存和协商缓存,再进行一次简单介绍:
1.强缓存: 不会向服务器发送请求,直接从缓存中读取资源,在 chrome 控制台的 Network 选项中可以看到该请求返回 200 的状态码,并且 size 显示 from disk cache 或 from memory cache 两种(灰色表示缓存)。 2.协商缓存: 向服务器发送请求,服务器会根据这个请求的 request header 的一些参数来判断是否命中协商缓存,如果命中,则返回 304 状态码并带上新的 response header 通知浏览器从缓存中读取资源;
共同点:都是从客户端缓存中读取资源; 区别是强缓存不会发请求,协商缓存会发请求。
对于浏览器端的缓存规则,是在 http 协议和 meta 标签中定义的。分别从两个维度:新鲜度和校验值,规定浏览器是否可以直接使用缓存中的副本,还是直接从服务器获取最新资源。
新鲜度(过期):浏览器缓存的有效期,缓存必须满足以下两个条件,浏览器才会认为是最新的,可以直接使用。
- 含有完整的过期时间控制头信息,并在有效期内。
- 浏览器已经使用过这个副本,并且在会话中已经检查过新鲜度。
校验值(验证):服务器返回资源的时候,会在响应头信息中带上资源实体标签 Entity Tag,可以用来作为浏览器再次请求过程的校验标识,如果发现校验标识不匹配,说明资源已经被修改过或过期,浏览器需要重新请求资源。
HTTP 协议头
http 请求和响应头中,与缓存相关的常见类型:
规则 | 消息报头 | 值/示例 | 类型 | 作用 |
---|---|---|---|---|
新鲜度 | Pragma | no-cache | 响应 | 告诉浏览器忽略资源的缓存副本,每次访问都需要去服务器拉取http1.0 中存在的字段,在 http1.1 已被抛弃,使用 Cache-Control 替代,但为了做 http 协议的向下兼容,很多网站依旧会带上这个字段 |
Expires | Mon, 15 Aug 2016 03:56:47 GMT | 响应 | 启用缓存和定义缓存时间。告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求http1.0 中存在的字段,该字段所定义的缓存时间是相对服务器上的时间而言的,如果客户端上的时间跟服务器上的时间不一致(特别是用户修改了自己电脑的系统时间),那缓存时间可能就没啥意义了。在 HTTP 1.1 版开始,使用 Cache-Control: max-age=秒替代 | |
Cache-Control | no-cache | 响应 | 告诉浏览器忽略资源的缓存副本,强制每次请求直接发送给服务器,拉取资源,但不是“不缓存” | |
no-store | 响应 | 强制缓存在任何情况下都不要保留任何副本 | ||
max-age=[秒] | 响应 | 指明缓存副本的有效时长,从请求时间开始到过期时间之间的秒数 | ||
public | 响应 | 任何路径的缓存者(本地缓存、代理服务器),可以无条件的缓存该资源 | ||
private | 响应 | 只针对单个用户或者实体(不同用户、窗口)缓存资源 | ||
Last-Modified | Mon, 15 Aug 2016 03:56:47 GMT | 响应 | 告诉浏览器这个资源最后的修改时间。服务器将资源传递给客户端时,会将资源最后更改的时间以“Last-Modified: GMT”的形式加在实体首部上一起返回给客户端只能精确到秒级,如果某些文件在 1 秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间 | |
If-Modified-Since | Mon, 15 Aug 2016 03:56:47 GMT | 请求 | 其值为上次响应头的 Last-Modified 值,再次向 web 服务器请求时带上头 If-Modified-Since。web 服务器收到请求后发现有头 If-Modified-Since 则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),包括更新 Last-Modified 的值,HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应 HTTP 304(无需请求,节省浏览),告知浏览器继续使用所保存的 cache | |
校验值 | ETag | “fd56273325a2114818df4f29a628226d” | 请求 | 告诉浏览器当前资源在服务器的唯一标识符(生成规则由服务器决定) |
If-None-Match | “fd56273325a2114818df4f29a628226d” | 请求 | 当资源过期时(使用 Cache-Control 标识的 max-age),发现资源具有 Etage 声明,则再次向 web 服务器请求时带上头 If-None-Match(Etag 的值)。web 服务器收到请求后发现有头 If-None-Match 则与被请求资源的相应校验串进行比对,决定返回 200 或 304 |
各种类型之间的关系和区别:
- Cache-Control 与 Expires:它两作用一样,都表明当前资源的有效期,控制浏览器是取缓存还是直接向服务器获取,Cache-Control 可以设置的更细致,如果同时设置,它的优先级高于 Expires。
- Last-Modified / ETag 与 Cache-Control / Expires:配置 Last-Modified/ETag 的情况下,浏览器再次访问 URL 的资源,还是会发送请求到服务器,询问文件是否已经修改,如果没有,服务器会给浏览器返回 304,浏览器直接从本地缓存中取就好了,反之,服务器会直接向浏览器返回数据。Cache-Control / Expires 检测本地缓存是否还在有效期内,在有效期内,直接使用本地缓存,阻止发送请求。如果同时设置,Cache-Control / Expiress 优先级更高。一般情况下,两者配合使用,因为即使服务器设置缓存时间, 当用户点击“刷新”按钮时,浏览器会忽略缓存继续向服务器发送请求,这时 Last-Modified/ETag 将能够很好利用 304,从而减少响应开销。
- Last-Modified 与 ETag:ETag 主要是为了解决 Last-Modified 比较难解决的问题:1、Last-Modified 标注的最后修改只能精确到秒级,如果某些文件在 1 秒钟以内,被修改多次的话,它将不能准确标注文件的新鲜度。2、如果某些文件会被定期生成,当有时内容并没有任何变化,但 Last-Modified 却改变了,导致文件没法使用缓存。3、有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形。ETag 是服务器自动生成或开发者生成对应资源在服务器的唯一标识符,能够更加精准控制缓存。两者可以一起使用,服务器优先验证 ETag,一致时,才会继续比对 Last-Mofifed,才决定是否要返回 304。
不能缓存的请求
也并非所有请求都可以被缓存,包括:
- post 请求无法被缓存。
- 需要根据 cookie、认证信息等决定输入内容的动态请求不能被缓存。
- http 响应头中不包含 Last-Modified/ETag,也不包含 Cache-Control/Expiress 的请求无法被缓存。
- http 信息头明确设置 Cache-Control:no-cache,pragma:no-cache 或 Cache-Control:max-age=0 浏览器不缓存时。
前端发版后浏览器缓存问题
在简单了解浏览器的缓存机制后,在开发完发布新版本后,在有些电脑上总需要强刷才能获取到最新版本的内容,这显然是不合理的,需要通过一些手段进行解决,通过一番查询,找到几种解决方案进行处理
解决方案
- 在 .html 页面加 meta 标签
1 | <meta http-equiv="pragram" content="no-cache"> |
- 后端 nginx 配置,让 index.html 不缓存
vue 默认配置,打包后 css 和 js 的名字后面都加了哈希值,不会有缓存问题,但是 index.html 在服务器端可能是有缓存的,需要在服务器配置不让缓存 index.html。
1 | location = /index.html { |
- 使用 Vue 脚手架的情况下:vue.config.js
1 | // 动态版本号 |
- 使用 webpack 的情况下:webpack.config.js
1 | const date = new Date() |
- 有新版本发布,及时拉取最新版本代码
有时候发布了新版本,用户不刷新或强制刷新,一直不能看不到最新版本代码,通过使用插件version.js,封装了套在切换页面时检查服务器是否有新版本,有新版本则直接强制刷新拉取最新版本代码,这样也解决了缓存问题,切换页面就能及时同步到最新版本代码。
使用方式,只需要两个位置导入使用:
1、「如果是原生开发,第一步可以改为手动创建文件,并每次修改版本号即可」在打包配置文件中(例如:vue.config.js)创建版本文件,因为只需要 build 时才需要创建版本文件,版本文件以时间戳为版本号,所以不需要操心。
1 | // 在 build 时,每次创建/更新版本文件 |
2、打包有了版本号,发布上去后,那就需要在合适位置,调用**version.getPro()**拉回来校验是否有新版本