浏览器缓存,即客户端缓存,是网页性能优化里静态资源优化的重要环节。
一、浏览器缓存基本认识
浏览器缓存,可分为强缓存和协商缓存两种。
-
浏览器在加载资源时,先根据这个资源的 http response header 里的 Expires 和 Cache-Control 字段判断它是否命中强缓存,如果命中,浏览器则从自己的缓存中加载此资源,不会发送请求至服务器
-
当没有命中强缓存时,浏览器会发送请求至服务器,服务器根据 http request header 里的 If-Modified-Since(上一次的请求的 response header 里的 Last-Modified 字段) 和 If-None-Match(上一次请求的 response header 里的 Etag 字段) 字段判断是否命中协商缓存,如果命中,服务器会将这个请求返回,但是不会返回请求的资源,浏览器则从缓存中加载这个资源
-
如果没有命中强缓存和协商缓存,服务器会返回此资源
二、强缓存
当浏览器对某个资源命中了强缓存时,返回的 http 状态码为 200,在 Chrome 浏览器中的开发者工具的 network 里面,size 会显示为 from memory cache(浏览器进程的内存缓存) 或者 from disk cache(系统的硬盘缓存)。
如下图:
(一)内存缓存和硬盘缓存比较
| 比较 | 读取速度 | 时效性 | 容量 | 匹配优先级 |
|---|---|---|---|---|
| 内存缓存 | 快速 | 进程关闭,内存清空 | 小 | 先 |
| 硬盘缓存 | 速度比内存缓存慢,需要重新解析文件,进行I/O操作 | 时效长 | 大 | 后 |
- 读取优先级:内存缓存 > 硬盘缓存
- 大文件,大概率存进硬盘中
- 当前系统内存使用率高时,文件优先存入硬盘中
- JS / 图片资源一般存入内存中,CSS 一般存入硬盘中
(二)强缓存的原理
利用 http response header 中的 Expires 和 Cache-Control 字段实现。
Expires:过期时间
Expires 是 http1.0 提出的一个表示资源过期时间的 header,它描述的是一个绝对时间,由服务器返回,用 GMT 格式的字符串表示,如:Expires:Thu, 31 Dec 2037 23:55:55 GMT,它的缓存原理是:
- 浏览器第一次和服务器请求资源时,服务器在返回这个资源的同时,在 response headers 里面增加 Expires 字段,如:
- 浏览器在接收到这个资源后,会把资源连同 response headers 一起缓存下来
- 浏览器再次请求这个资源时,会先从缓存中找,找到这个资源后,会将缓存中的 response headers 里的 Expires 字段的时间与当前时间对比,如果当前时间早于 Expires 时间,就能命中缓存
- 没有命中缓存时,后续从服务器加载这个资源,会更新 response headers
Expires 弊端: Expires 是由服务器返回的一个时间,当服务器与客户端时间相差较大的情况下,比如随意修改了客户端的时间,就会影响缓存命中的结果。
因此在 HTTP1.1 时,在 response headers 里新增了一个 cache-control 字段
Cache-Control:有效时间
以秒为单位,会覆盖 Expires
原理:基本同 Expires,唯一的区别是,浏览器计算资源失效时间,是根据第一次请求资源的时间 + Cache-Control 里返回的资源有效时间,与客户端的当前时间做对比。就不存在 Expires 服务器与客户端时间有差异导致的问题了。
Cache-Control 属性值有以下几种,不同属性间用逗号 , 间隔:
- max-age(单位为 s)指定设置缓存最大的有效时间
- s-maxage(单位为 s)同 max-age,只用于共享缓存(比如 CDN 缓存),s-maxage 会覆盖 max-age 和 Expires
- public 指定响应会被缓存,并且在多用户间共享,未指定时,默认值为 public
- private 响应只作为私有的缓存(见下图),不能在用户间共享。如果要求 HTTP 认证,响应会自动设置为 private
- no-cache 指定不缓存响应,表明浏览器在资源缓存前要向服务器确认资源是否被更改, 比如:
- no-store 绝对禁止缓存
- must-revalidate 指定如果页面是过期的,则去服务器进行获取
(三)强缓存的管理
在实际开发中,我们会碰到需要强缓存和不需要强缓存的场景,通常有两种方式来设置是否启用强缓存: 1.通过代码的方式,在 web 服务器返回的响应中添加 Expires 和 Cache-Control Header;
- 通过配置 web 服务器的方式,让 web 服务器在响应资源的时候统一添加 Expires 和 Cache-Control Header。
由于在开发的时候不会专门去配置强缓存,而浏览器又默认会缓存图片,css和js等静态资源,所以开发环境下经常会因为强缓存导致资源没有及时更新而看不到最新的效果,解决这个问题的方法有很多,常用的有以下几种:
- 直接 ctrl+f5,这个办法能解决页面直接引用的资源更新的问题;
- 使用浏览器的隐私模式开发;
- 如果用的是 chrome,可以 F12 在 network 那里把缓存给禁掉(这是个非常有效的方法):
-
在开发阶段,给资源加上一个动态的参数,如 css/index.css?v=0.0001,由于每次资源的修改都要更新引用的位置,同时修改参数的值,所以操作起来不是很方便,除非你是在动态页面比如 jsp 里开发就可以用服务器变量来解决(v=${sysRnd}),或者你能用一些前端的构建工具来处理这个参数修改的问题;
-
如果资源引用的页面,被嵌入到了一个 iframe 里面,可以在 iframe 的区域右键单击重新加载该页面,以 chrome 为例:
-
如果缓存问题出现在 ajax 请求中,最有效的解决办法就是 ajax 的请求地址追加随机数;
-
还有一种情况就是动态设置 iframe 的 src 时,有可能也会因为缓存问题,导致看不到最新的效果,这时候在要设置的 src 后面添加随机数也能解决问题;
-
如果你用的是 grunt 和 gulp 这种前端工具开发,通过它们的插件比如 grunt-contrib-connect 来启动一个静态服务器,则完全不用担心开发阶段的资源更新问题,因为在这个静态服务器下的所有资源返回的 respone header 中,cache-control 始终被设置为不缓存: com
(四)强缓存的应用
通过给 http response header 的 Expires 和 Cache-Control 设置超长的时间这种方式,提高页面加载速度,同时会带来一个新的问题:发布时资源更新问题。
比如,一张图,用户在第一次访问时浏览器已经缓存了该资源,当网站发布新版本时,同名替换了该图片,由于强缓存,用户在不清理/禁用缓存或强制刷新情况下,就看不到这张新图了。
解决方案,可以参考这几篇文章:
- www.zhihu./question/20…
- fis.baidu.com/fis3/api/in…
- ecomfe.github.io/edp/doc/ini…
- www.jianshu.com/p/906c61a71…
三、协商缓存
协商缓存的 http 请求返回状态码是 304,如下图:
(一)协商缓存的原理
协商缓存是利用【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】这两对 Header 来管理的。
【Last-Modified,If-Modified-Since】
-
浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在 respone headers 加上 Last-Modified 字段,它表示这个资源在服务器上的最后修改时间:
-
浏览器再次跟服务器请求这个资源时,在 request headers 上加上 If-Modified-Since 字段,这个字段的值就是上一次请求时返回的 Last-Modified 的值:
-
服务器再次收到资源请求时,根据浏览器传过来 If-Modified-Since 和资源在服务器上的最后修改时间判断资源是否有变化,如果没有变化则返回 304,但是不会返回资源内容;如果有变化,就正常返回资源内容;
-
浏览器收到304的响应后,就会从缓存中加载资源;
-
如果协商缓存没有命中,浏览器直接从服务器加载资源时,Last-Modified 字段会被更新。
【ETag、If-None-Match】
有时候也会出现,服务器上资源有变化,但是最后更新时间没有变化的情况,这时候,【Last-Modified、If-Modified-Since】就不可靠了。所以,就出现了【ETag、If-None-Macth】这对新字段。
ETag 是服务器根据当前请求的资源生成的一个唯一标识,与最后修改时间无关,只要资源有变化,Etag 就不同。
原理与【Last-Modified、If-Modified-Since】一致。
(二)协商缓存的管理
协商缓存需要配合强缓存一起使用,同时需要注意以下:
- 分布式系统里多台机器间文件的 Last-Modified 必须保持一致,以免负载均衡到不同机器导致比对失败;
- 分布式系统尽量关闭掉 ETag (每台机器生成的 ETag 都会不一样)。
四、浏览器行为对缓存的影响
- ctrl + F5 会强制刷新页面,跳过强缓存和协商缓存,直接从服务器获取资源;
- F5 会刷新页面,跳过强缓存