原文链接:http://www.cun-xu.cn/index.php/2018/12/26/浏览器缓存/
今天我们来说一下浏览器缓存的问题,缓存可以减少网络IO的消耗,提高访问速度。浏览器缓存是一种操作简单、效果显著的前端性能优化手段。
关于缓存的头部字段包括:
cache-control(缓存头)
- 每个资源都可通过
cache-control
HTTP标头定义其缓存策略 cache-control
指令控制谁在什么条件下可以缓存响应以及可以缓存多少
可缓存性
public
:
即使它有相关联的http身份验证,甚至响应状态代码通常无法缓存,也可以缓存响应。大多数情况下,public
不是必需的,因为明确的缓存信息(例如max-age
)已表示响应是可以缓存的。
private
:
浏览器可以缓存private
响应。不过,这些响应通常只为单个用户缓存,因此不允许任何中间缓存对其进行缓存。例如,用户的浏览器可以缓存包含私人信息的HTML网页,但CDN却不能进行缓存。
no-cache
:
表示必须先与服务器确认返回的响应是否发生了变化,然后才能使用该响应来满足后续对同一网址的请求。因此,如果存在合适的验证令牌(Etag
),no-cache
会发起往返通信来验证缓存的响应,但如果资源未发生变化,则可避免下载。有的时候只设置no-cache
防止缓存还是不够保险,还可以加上private
指令,将过期时间设为过去的时间。
到期
max-age=(seconds
)
指令指定从请求的时间开始,允许获取的响应被重用的最长时间(单位:秒)。例如,"max-age=60
"表示可在接下来的60秒缓存和重用响应。
s-maxage=(seconds
):同max-age
,只用于共享缓存(比如CDN缓存)
比如,当s-maxage=60
时,在这60秒钟,即使更新了CDN的内容,浏览器也不会进行请求。也就是说max-age用与普通缓存,而s-maxage
用于代理缓存,如果存在s-maxage
,则会覆盖掉max-age
和Expires
header。
max-stale=(seconds
)
为发起端设置,即使缓存已经到期,但在max-stale
设置的时间内还可以使用过期的缓存。
重新验证
must-revalidate
缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源。
proxy-revalidate
与must-revalidate
作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。
其他
no-store
直接禁止浏览器以及所有中间缓存存储任何版本的返回响应,例如,包含个人隐私数据或银行业务数据的响应。每次用户请求该资源时,都会向服务器发送请求,并下载完整的响应。
no-transform
不得对资源进行转换或转变。Content-Encoding, Content-Range, Content-Type等HTTP头不能由代理修改。例如,非透明代理可以对图像格式进行转换,以便节省缓存空间或者减少缓慢链路上的流量。 no-transform指令不允许这样做。
Pragma
当该字段值为no-cache
的时候。会告诉浏览器不要对该资源缓存,及每一次都得向服务器发送一次响应。
res.serHeader('Pragma','no-cache') //禁止缓存
res.setHeader('Cache-Control','public,max-age=120') //2分钟
通过Pragma
来禁止缓存,通过Cache-Control
设置两分钟缓存,但是重新访问我们会发现浏览器会再次发起一次请求,说明了Pragma的优先级高于Cache-Control
。
Expires
缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires = max-age + 请求时间
,需要和Last-modified
结合使用。但在上面我们提到过,cache-control
的优先级更高。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前可以直接从浏览器缓存取数据,而无需再次请求。
Last-Modified 上次修改时间
服务器端文件的最后修改时间,需要和catch-control
共同使用,是检查服务器资源是否更新的一种方式。当浏览器再次进行请求时,会向服务器传送If-Modified-Since
报头,询问Last-Modified
时间点之后资源是否被修改过。如果没有修改,则返回码为304,使用缓存;如果修改过,则再次去服务器请求资源,返回码和首次请求相同为200,资源为服务器最新资源。
Etag 数据签名
根据试题内容生成一段哈希字符串,标识资源的状态,由服务器产生。浏览器会将这串字符串传回服务器,验证资源是否已被修改,如果没有修改,过程如下:
使用Etag可以解决Last-modified存在的一些问题:
- 某些服务器不能精确得到资源的最后修改时间,这样就无法通过最后修改时间判断资源是否更新
- 如果某些资源修改非常频繁,在秒一下的时间内进行修改,而
last-modified
只能精确到秒。 - 一些资源的最后修改时间改变了,但是内容没改变,使用
Etag
就认为资源还是没有修改的
根据查到的资料显示,缓存头的优先级是这样的:
Pragma > Cache-Control > Expires > Etag > Last-Modified
缓存策略
至于缓存策略的制定,我们可以参照Google官方的这张图
由于图上都是英文,我猜像我这样的菜鸟都看不懂,这里借资料来解释一下:
当我们的资源内容不可复用时,直接为
Cache-Control
设置no-store
,拒绝一切形式的缓存;否则考虑是否每次都需要向服务器进行缓存有效确认,如果需要,那么设Cache-Control
的值为no-cach
e;否则考虑该资源是否可以被代理服务器缓存,根据其结果决定是设置为private
还是public
;然后考虑该资源的过期时间,设置对应的max-age
和s-maxage
值;最后,配置协商缓存需要用到的Etag、Last-Modified
等参数。
根据上面的基础知识和解读,我们可以知晓:在制定缓存策略时,需要牢记以下技巧:
- 使用一致的网址:如果您在不同的网址上提供相同的内容,将会多次获取和存储这些内容。提示:网址区分大小写。
- 确保服务器提供验证令牌(
Etag
):有了验证令牌,当服务器上的资源未发生变化时,就不需要传送相同的字节。 - 确定中间缓存可以缓存哪些资源:对所有用户的响应完全相同的资源非常适合由CDN以及其他中间缓存进行缓存。
- 为每个资源确定最佳缓存周期:不同的资源可能有不同的更新要求。为每个资源审核并确定合适的
max-age
。 - 确定最合适您的网站的缓存层次结构:您可以通过为HTML文档组合使用包含内容指纹的资源网址和短时间或
no-cache
周期,来控制客户端获取更新的速度。 - 最大限度减少搅动:某些资源的更新比其他资源频繁。如果资源的特定部分(例如JavaScript函数或CSS样式集)会经常更新,可以考虑将其代码作为单独的文件提供。这样一来,每次获取更新时,其余内容(例如变化不是很频繁的内容库代码)可以从缓存获取,从而最大限度减少下载的内容大小
文章的最后我们还要说一下内存缓存(from memory cache
)和硬盘缓存(from disk cache
)。
内存缓存
内存缓存有两个特点,分别是快速读取和时效性。
- 快速读取:内存换粗会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以便下次运行时的快速读取
- 时效性:一旦该进程关闭,则该进程的内存会被清空
硬盘缓存
硬盘缓存- 则是将缓存直接写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。
那么哪些文件会被放入内存中呢?
事实上,这个划分规则,一直以来是没有定论的。不过想想也可以理解,内存时有限的,很多时候需要先考虑即时呈现的内存余量,再根据具体的情况决定分配给内存和磁盘的资源量的比重——资源存放的位置具有一定的随机性。
虽然划分规则没有定论,但根据日常开发中观察的结果,我们至少可以总结出这样的规律:资源存不存内存,浏览器秉承的是“节约原则”。我们发现,Base64格式的图片,几乎永远可以被塞进memory cache,这可以视作浏览器为节省渲染开销的“自保行为”;此外,体积不大的 JS、CSS 文件,也有较大地被写入内存的几率——相比之下,较大的 JS、CSS 文件就没有这个待遇了,内存资源是有限的,它们往往被直接甩进磁盘。
浏览器读取缓存的顺序为memory –> disk
好啦,着急忙慌整完了,正好零点,碎觉碎觉。
明天还要考试,关老爷保佑!!!