浏览器缓存是什么
浏览器将最近请求过的文档存储在本地磁盘,当访问者再次访问同一页面时,浏览器直接从本地磁盘加载文档,而不会重新的请求的行为就是浏览器缓存。
浏览器缓存的优点:
- 减少冗余的数据传输,节省网络资源,减少服务器负担
- 提升网站的性能,加快客户端加载网页的速度
在前端开发面试中,浏览器缓存是web性能优化面试题中很重要的一个知识点,从而说明浏览器缓存是提升web性能的一大利器。
下图展示了浏览器访问一个有缓存的页面的时候的决策流程。
浏览器缓存的分类
浏览器缓存主要有两类:强缓存和协商缓存。
一、强缓存:不用请求服务器,直接使用本地缓存,利用http响应头中的Exprires或Cache-Cantrol实现。
Expires或者Cache-Control两个字段表示资源的缓存时间。
Expires 是http1.0时的规范,是服务器返回一个的绝对时间(GMT格式的时间字符串,比如Expires:Mon,18 Oct 2066 23:59:59 GMT),这个时间代表着这个资源的失效时间,在此时间之前,即命中缓存。这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱。所以,在http1.1中,提出了一个新的response header,就是Cache-Control。
Cache-Control主要是利用该字段的max-age值来进行判断,它是一个相对时间,例如Cache-Control:max-age=3600,代表着资源的有效期是3600秒。cache-control除了该字段外,还有下面几个比较常用的设置值:
- no-cache:不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。
- no-store:直接禁止游览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。
- public:可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器。
- private:只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存。
Cache-Control与Expires可以在服务端配置同时启用,同时启用的时候Cache-Control优先级高。
二、协商缓存:浏览器发现本地有资源的副本,但是不太明确要不要使用,于是去问问服务器。
协商缓存是利用的是两对Header:
- 第一对:Last-Modified、If-Modified-Since
- 第二对:ETag、If-None-Match
这两组搭档都是成对出现的,即第一次请求的响应头带上某个字段(Last-Modified或者Etag),则后续请求则会带上对应的请求字段(If-Modified-Since或者If-None-Match),若响应头没有Last-Modified或者Etag字段,则请求头也不会有对应的字段。
Last-Modify/If-Modify-Since
- 浏览器第一次请求一个资源的时候,服务器返回的response header中会加上Last-Modify,标识该资源的最后修改时间,例如Last-Modify: Thu,31 Dec 2037 23:59:59 GMT。
- 当浏览器再次请求该资源时,request header中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。
- 服务器收到第二次请求时的If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存。
- 如果命中缓存,则返回304,并且不会返回资源内容,并且不会返回Last-Modify。
Last-Modified、If-Modified-Since一般来说都是非常可靠的,但有可能出现的问题是:服务器上的资源变化了,但是最后的修改时间却没有变化。这一对header就无法解决这种情况。于是,ETag/If-None-Match就诞生了。
ETag/If-None-Match
- ETag是一个资源校验码,它可以保证每一个资源是唯一的,资源变化都会导致ETag变化,跟最后修改时间无关,所以也就很好地补充了Last-Modified的不足。
- 浏览器第一次请求一个资源,服务器在返回这个资源的同时,会加上ETag这个 response header,这个header是服务器根据当前请求的资源生成的唯一标识。
- 浏览器再次请求这个资源时,会加上If-None-Match这个 request header,这个header的值就是上一次返回的ETag的值。
- 服务器第二次请求时,会对比浏览器传过来的If-None-Match和服务器重新生成的一个新的ETag,判断资源是否有变化。如果没有变化则返回304 Not Modified,但不返回资源内容(此时,由于ETag重新生成过,response header中还会把这个ETag返回,即使这个ETag并无变化)。如果有变化,就正常返回资源内容(继续重复整个流程)。
- 浏览器如果收到304的响应,就会从缓存中加载资源。
Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。 强缓存与协商缓存的区别可以用下表来表示:
缓存类型 | 获取资源形式 | 状态码 | 发送请求到服务器 |
---|---|---|---|
强缓存 | 从缓存取 | 200(from cache) | 否,直接从缓存取 |
协商缓存 | 从缓存取 | 304(Not Modified) | 否,通过服务器来告知缓存是否可用 |
用户行为对缓存的影响
用户操作 | Expires/Cache-Control | Last-Modied/Etag |
---|---|---|
地址栏回车 | 有效 | 有效 |
页面链接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进回退 | 有效 | 有效 |
F5刷新 | 无效 | 有效 |
Ctrl+F5强制刷新 | 无效 | 无效 |
如何控制缓存
一种方法是在所有静态资源文件后面添加随机时间戳,例如
<script type="text/javascript" src="https://www.demo.com/demo.js?v=1234"></script>
每次修改demo.js之后修改v后面的时间戳,这样浏览器就会忽略缓存从服务器请求新的文件。
但是,由于html页面没有加时间戳,它页面依然是使用的缓存,html又是所有静态资源的载体,所以其他资源也是依然使用的缓存。
还有一种方法是在meta标签上添加cache-control,如下:
<!--设置过期时间设置0为直接过期并清除缓存-->
<meta http-equiv="Expires" content="0">
<!--设置不缓存页面-->
<meta http-equiv="Pragma" content="no-cache">
<!--设置不修改消息存储-->
<meta http-equiv="Cache-control" content="no-cache">
<!--同上-->
<meta http-equiv="Cache" content="no-cache">
但是,这样做并没有什么用,完全没办法避免浏览器的缓存,添加cache-control没错,但是需要在响应头添加,所以我们需要修改服务器配置。以下是nginx配置的参考:
# 利用正则表达式匹配静态资源目录
location ~ /dest/(.*) {
root E:/Workspace/my;
add_header Cache-Control no-cache;
add_header Cache-Control private;
expires 0s;
}
如此配置后,dest目录下的所有文件将不再缓存,每次都从服务器端请求了。