---
title: 前端缓存
author: 王许晓
date: 2021-11-11 10:00
---


**缓存是一种保存资源副本并在下次请求时直接使用该副本的技术**
## 缓存的种类
​

我相信只要你经常使用某个浏览器(Chrome,Firefox,IE等)，肯定知道这些浏览器在设置里面都是有个清除缓存功能，这个功能存在的作用就是删除存储在你本地磁盘上资源副本，也就是清除缓存。
缓存存在的意义就是当用户点击back按钮或是再次去访问某个页面的时候能够更快的响应。尤其是在多页应用的网站中，如果你在多个页面使用了一张相同的图片，那么缓存这张图片就变得特别的有用。



<!-- more -->
### 代理服务器缓存
代理服务器缓存原理和浏览器端类似，但规模要大得多，因为是为成千上万的用户提供缓存机制，大公司和大型的ISP提供商通常会将它们设立在防火墙上或是作为一个独立的设备来运营。
由于缓存服务器不是客户端或是源服务器的一部分，它们存在于网络中，请求路由必须经过它们才会生效，所以实际上你可以去手动设置浏览器的代理，或是通过一个中间服务器来进行转发，这样用户自然就察觉不到代理服务器的存在了。
代理服务器缓存就是一个共享缓存，不只为一个用户服务，经常为大量用户使用，因此在减少相应时间和带宽使用方面很有效：因为同一个缓存可能会被重用多次。
### 网关缓存
也被称为代理缓存或反向代理缓存，网关也是一个中间服务器，网关缓存一般是网站管理员自己部署，从让网站拥有更好的性能。🙂
CDNS(网络内容分发商)分布网关缓存到整个（或部分）互联网上，并出售缓存服务给需要的网站，比如国内的七牛云、又拍云都有这种服务。
### 数据库缓存
数据库缓存是指当我们的应用极其复杂，表自然也很繁杂，我们必须进行频繁的进行数据库查询，这样可能导致数据库不堪重负，一个好的办法就是将查询后的数据放到内存中，下一次查询直接从内存中取就好了。关于数据库缓存本篇不会展开。
​


## 浏览器的缓存策略
> 浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。

```http
Age:23146
Cache-Control:max-age=2592000
Date:Tue, 28 Nov 2021 12:26:41 GMT
ETag:W/"5a1cf09a-63c6"
Expires:Thu, 28 Dec 2021 05:27:45 GMT
Last-Modified:Tue, 28 Nov 2017 05:14:02 GMT
Vary:Accept-Encoding
```
### 强缓存阶段
以上请求头来自百度首页某个CSS文件的响应头。去除了一些和缓存无关的字段，只保留了以上部分。Expires是HTTP/1.0中的定义缓存的字段，它规定了缓存过期的一个绝对时间。Cache-Control:max-age=2592000是HTTP/1.1定义的关于缓存的字段，它规定了缓存过期的一个相对时间。
这就是强缓存阶段，当浏览器再次试图访问这个CSS文件，发现有这个文件的缓存，那么就判断根据上一次的响应判断是否过期，如果没过期，使用缓存。否则重新加载文件。
### 协商缓存阶段
利用ETag和Last-Modified这两个字段浏览器可以进入协商缓存阶段，当浏览器再次试图访问这个CSS文件，发现缓存过期，于是会在本次请求的请求头里携带If-Moified-Since和If-None-Match这两个字段，服务器通过这两个字段来判断资源是否有修改，如果有修改则返回状态码200和新的内容，如果没有修改返回状态码304，浏览器收到200状态码，该咋处理就咋处理(相当于首次访问这个文件了)，发现返回304，于是知道了本地缓存虽然过期但仍然可以用，于是加载本地缓存。然后根据新的返回的响应头来设置缓存。
```http
If-Moified-Since: Tue, 28 Nov 2017 05:14:02 GMT
If-None-Match: W/"5a1cf09a-63c6"
```
### 启发式缓存阶段
```http
Age:23146
Cache-Control: public
Date:Tue, 28 Nov 2017 12:26:41 GMT
Last-Modified:Tue, 28 Nov 2017 05:14:02 GMT
Vary:Accept-Encoding
```
根据响应头中2个时间字段 Date 和 Last-Modified 之间的时间差值，取其值的10%作为缓存时间周期。
这就是启发式缓存阶段。这个阶段很容让人忽视，但实际上每时每刻都在发挥着作用。所以在今后的开发过程中如果遇到那种默认缓存的坑，不要生气，浏览器只是在遵循启发式缓存协议而已。
![](https://cdn.nlark.com/yuque/0/2021/svg/1207482/1636550297609-0dfb5f49-e0fa-4945-8b59-055ee7461460.svg#clientId=u72078c95-c63b-4&from=paste&id=ubf82cf4e&margin=%5Bobject%20Object%5D&originHeight=795&originWidth=782&originalType=url&ratio=1&status=done&style=shadow&taskId=ub8a50233-3f0a-4ec3-8318-2e1f72589ae)
## 浏览器缓存控制
### Cache-Control
##### 请求指令
| 指令 | 参数 | 说明 |
| --- | --- | --- |
| no-cache | 无 | 强制源服务器再次验证 |
| no-store | 无 | 不缓存请求或是响应的任何内容 |
| max-age=[秒] | 缓存时长，单位是秒 | 缓存的时长，也是响应的最大的Age值 |
| min-fresh=[秒] | 必需 | 期望在指定时间内响应仍然有效 |
| no-transform | 无 | 代理不可更改媒体类型 |
| only-if-cached | 无 | 从缓存获取 |
| cache-extension | 无 | 新的指令标记(token) |

##### 响应指令
| 指令 | 参数 | 说明 |
| --- | --- | --- |
| public | 无 | 任意一方都能缓存该资源(客户端、代理服务器等) |
| private | 可省略 | 只能特定用户缓存该资源 |
| no-cache | 可省略 | 缓存前必须先确认其有效性 |
| no-store | 无 | 不缓存请求或响应的任何内容 |
| no-transform | 无 | 代理不可更改媒体类型 |
| must-revalidate | 无 | 可缓存但必须再向源服务器进确认 |
| proxy-revalidate | 无 | 要求中间缓存服务器对缓存的响应有效性再进行确认 |
| max-age=[秒] | 缓存时长，单位是秒 | 缓存的时长，也是响应的最大的Age值 |
| s-maxage=[秒] | 必需 | 公共缓存服务器响应的最大Age值 |
| cache-extension | - | 新指令标记(token) |

## 服务端缓存控制
当Expires和Cache-Control:max-age=xxx同时存在的时候取决于缓存服务器应用的HTTP版本。应用HTTP/1.1版本的服务器会优先处理max-age，忽略Expires，而应用HTTP/1.0版本的缓存服务器则会优先处理Expires而忽略max-age。
## 用户操作行为对缓存的影响
| 操作 | 说明 |
| --- | --- |
| 打开新窗口 | 如果指定cache-control的值为private、no-cache、must-revalidate,那么打开新窗口访问时都会重新访问服务器。而如果指定了max-age值,那么在此值内的时间里就不会重新访问服务器,例如：Cache-control: max-age=5 表示当访问此网页后的5秒内不会去再次访问服务器. |
| 在地址栏回车 | 如果值为private或must-revalidate,则只有第一次访问时会访问服务器,以后就不再访问。如果值为no-cache,那么每次都会访问。如果值为max-age,则在过期之前不会重复访问。 |
| 按后退按扭 | 如果值为private、must-revalidate、max-age,则不会重访问,而如果为no-cache,则每次都重复访问. |
| 按刷新按扭 | 无论为何值,都会重复访问.（可能返回状态码：200、304，这个不同浏览器处理是不一样的，FireFox正常，Chrome则会启用缓存(200 from cache)） |
| 按强制刷新按钮 | 当做首次进入重新请求(返回状态码200) |

## HTML5的缓存
这部分准确的说应该叫离线存储。现在比较普遍用的是Appcache，但Appcache已经从web标准移除了，在可预见的未来里，ServiceWorker可能会是一个比较适合的解决方案。
### Appcache
这是HTML5的一个新特性，通过离线存储达到用户在没有网络连接的情况下也能访问页面的功能。离线状态下即使用户点击刷新都能正常加载文档。
使用方法如下，在HTML文件中引入appcache文件:
```html
<!DOCTYPE html>
<html manifest="manifest.appcache">
<head>
  <meta charset="UTF-8">
  <title>***</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>
```
web 应用中的 manifest 特性可以指定为缓存清单文件的相对路径或一个绝对 URL(绝对 URL 必须与应用同源)。缓存清单文件可以使用任意扩展名，但传输它的 MIME 类型必须为 text/cache-manifest。
下面是一个完整的缓存清单文件的示例。
```http
CACHE MANIFEST
# 注释：需要缓存的文件，无论在线与否，均从缓存里读取
# v1 2017-11-30
# This is another comment
/static/logo.png

# 注释：不缓存的文件，始终从网络获取
NETWORK:
example.js

# 注释：获取不到资源时的备选路径，如index.html访问失败，则返回404页面
FALLBACK:
index.html 404.html
```
**注意：**主页一定会被缓存起来的，因为AppCache主要是用来做离线应用的，如果主页不缓存就无法离线查看了，因此把index.html添加到NETWORK中是不起效果的。
实际上这个特性已经web标准中删除，但现在为止还有很多浏览器支持它，所以这里提一下。
AppCache之所以不受待见我想了下面几个原因：

1.  一旦使用了manifest后，没办法清空这些缓存，只能更新缓存，或者得用户自己去清空浏览器的缓存； 
1.  假如更新的资源中有一个资源更新失败了，那么所有的资源就会全部更新失败，将用回上一版本的缓存 
1.  主页会被强制缓存(使用了manifest的页面)，并且无法清除 
1.  appache文件可能会无法被及时更新，因为各大浏览器对于appcache文件的处理方式不同 
1.  以上几个弊端一旦出问题，会让用户抓狂更会让开发者抓狂 
# 实现html页面如何不缓存js
不缓存JS的方法其实挺简单，下面给出代码。
原理就是可以给页面后面设定个不同的值，让页面保持没错访问的不同即可达到不缓存的目的了。但是这种方式解决不了代理服务器缓存。
```javascript
<script>
    document.write("<script type='text/javascript' src='/storage/exam/js/js_url.js?ver="+Math.random()+"'><\/script>");
</script>
```


