作为一个前端开发者,每天都在和浏览器打交道,对于浏览器缓存,相信都不会陌生,同时它也是我们日常开发中存在的一个非常重要的优化
手段,无论在节省带宽、提高加载和渲染速度、减少网络阻塞,以及提高用户体验上,都有重要的作用。
这是我们输入网址后,最开始的一个缓存;通常我们输入一个网址,它包含了域名
和端口
可以指定唯一的IP地址,然后建立连接进行通信,而域名查找IP地址的过程就是DNS解析
。
www.baidu.com (域名) - DNS解析 -> 180.76.76.76 (IP地址)
这个过程会对网络请求带来一定的损耗,所以浏览器在第一次获取到IP地址后,会将其缓存起来。下次相同域名再次发起请求时,浏览器会先查找本地缓存,如果缓存有效,则会直接返回该IP地址,否则会继续开始寻址之旅。
关于寻址过程请看我之前的文章 浏览器从输入URL到页面渲染加载的过程(浏览器知识体系整理)
备注:
memory cache =》浏览器本地缓存
disk cache =》 硬盘缓存
先在 浏览器缓存
中查找,如果有,直接加载。
如果 浏览器缓存
中不存在,则在 硬盘
中查找,这里又细分:
1)如果有强缓存且未失效,则使用强缓存,不请求服务器。
2)如果有强缓存但已失效,使用协商缓存,比较后确定 304 还是 200;
如果硬盘中也不存在,向服务器发起网络请求
请求获取的资源缓存到硬盘和内存。
下面将从 缓存位置 和 缓存策略 两个角度介绍浏览器缓存。
是浏览器内存中的缓存,相比于 disk cache 它的特点是读取速度快,但容量小,且时效性短;不受开发者控制,也不受HTTP协议头的约束。一旦浏览器 tab
页关闭,memory cache
就将被清空,再次重新打开相同页面时不再出现from memory cache
的情况。
硬盘缓存取决于HTTP中的响应头信息,它也是浏览器缓存中最重要的内容。因为DNS缓存它主要是做一个ip地址查找并且是自主完成的,memory cache 也是不受控制,算是一个黑盒。所以剩下的可以受我们控制的硬盘缓存的重要性就不言而喻了,大多优化手段也是针对硬盘缓存
。
根据 HTTP 响应头
的各类字段进行判定资源的缓存规则,比如是否可以缓存,什么时候过期,过期之后是否需要重新发起请求呢?相比于 memory cache 的 disk cache 拥有存储空间时间长等优点。
HTTP所控制下的 disk cache 缓存分为强缓存
和协商缓存
。
根据 HTTP header
的字段将缓存分为两个部分,分别是强缓存
和协商缓存
。
过期时间
内,是的话直接从本地缓存中读取资源,不与服务器进行通信。资源没有更新
,则返回状态码 304
Not Modified,告诉浏览器可以使用本地缓存;否则返回新的资源内容。强缓存优先级高于协商缓存
,但是协商缓存可以更加灵活地控制缓存的有效性。强缓存的字段有:Expires
和 Cache-Control
。协商缓存的字段有:Last-Modified
和 ETag
。
Expires
和 Cache-Control
都被设置的时候,浏览器会优先考虑后者。Last-Modified
和 ETag
都被设置的时候,浏览器会优先考虑后者。当客户端发出一个请求到服务器,服务器希望你把资源缓存起来,于是在响应头
中加入了这些内容:
Cache-Control: max-age=3600 // 我希望你把这个资源缓存起来,缓存时间是3600秒(1小时)
Expires: Mon Oct 17 2023 16:10:32 GMT // 到达指定时间过期
Date: Mon Oct 16 2023 13:30:30 GMT
Etag:W/"121-171ca289ebf",// (后面协商缓存内容)这个资源的编号是W/"121-171ca289ebf"
Last-Modified: Mon Oct 16 2023 09:20:10 GMT ,// (后面协商缓存内容)这个资源的上一次修改时间
Cache-Control
和 Expires
分别是HTTP/1.1 和 HTTP/1.0的内容,为了兼容 HTTP/1.0 和 HTTP/1.1,实际项目中两个字段我们都会设置。
浏览器收到这个响应之后就会做下面的事情
这一次的记录非常重要,它为以后浏览器要不要去请求服务器提供了依据。
判断缓存是否有效就是通过把 max-age + Date
,得到一个过期时间,看看这个过期时间是否大于当前时间,如果是,则表示缓存还没有过期,仍然有效,如果不是,则表示缓存失效。
Expires
是HTTP/1.0
的字段,表示缓存过期时间。Expires 需要在服务端配置(具体配置也根据服务器而定),浏览器会根据该过期日期与客户端时间对比,如果过期时间还没到,则会去缓存中读取该资源,如果已经到期了,则浏览器判断为该资源已经过期需要重新从服务端获取。由于 Expires 是一个绝对时间
,所以会局限于客户端时间的准确性,从而可能会出现浏览器判断缓存失效的问题。所以出现了Cache-Control,如下是一个 Expires 示例,是一个日期/时间:
Expires: Mon Oct 17 2023 16:10:32 GMT
到了HTTP/1.0
版本,已更改为通过Cache-Control
的max-age
来记录了。
Cache-Control
是 http1.1
时出现的响应头信息,主要通过Cache-Control
的max-age
来记录。下面是几个比较常用的设置值:
max-age:
最大缓存时间,它是一个相对时间
,值的单位是秒,在该时间内,浏览器不需要向浏览器请求。这个设置解决了 Expires 中由于客户端系统时间不准确而导致缓存失效的问题。no-cache:
跳过强缓存,直接进入协商缓存阶段。no-store:
禁止使用缓存,每次都要重新请求数据,不会被缓存到内存和硬盘。public:
响应可以被任何对象(客户端、代理服务器等)缓存。private:
响应只能被客户端缓存。Cache-Control 的值是可以混合使用的,比如:
Cache-Control: private, max-age=0, no-cache
当强缓存失效的时候,则会进入到协商缓存阶段。
一旦发现强缓存无效,浏览器会发送一个请求到服务器,服务器根据请求header
中的部分信息来判断资源是否更新。如果没有更新,返回304重定向,告诉浏览器资源未更新,可继续使用本地的缓存;否则返回 状态码200 和 新的资源内容,浏览器缓存新的内容。
这里的请求头header,就是加入了
If-Modified-Since: Mon Oct 16 2023 09:20:10 GMT 你好,你曾经告诉我,这个资源的上一次修改时间是格林威治时间2023-10-16 09:20:10,请问这个资源在这个时间之后有发生变动吗?
If-None-Match: W/"121-171ca289ebf" 你好,你曾经告诉我,这个资源的编号是W/"121-171ca289ebf,请问这个资源的编号发生变动了吗?
其实响应头和请求头的对应关系就是 Last-Modify/If-Modify-Since
和 ETag/If-None-Match
。
之所以要发两个信息,是为了兼容不同的服务器,因为有些服务器只认If-Modified-Since
,有些服务器只认If-None-Match
,有些服务器两个都认,但是一般来说 If-None-Match
的优先级高于 If-Modified-Since
浏览器第一次请求一个资源的时候,服务器返回的 header 中会加上 Last-Modify
,Last-Modify
是一个时间标识该资源的最后修改时间。
当浏览器再次请求该资源时,请求头中会包含 If-Modify-Since
,该值为缓存之前返回的 Last-Modify
。服务器收到 If-Modify-Since
后,根据资源的最后修改时间判断资源是否更新。
缺点:如果资源更新的速度是小于 1 秒的,那么该字段将失效,因为 Last-Modified 时间是精确到秒的。所以有了 ETag。
与 Last-Modify/If-Modify-Since 不同的是,Etag/If-None-Match 返回的是一个校验码。ETag
可以保证每一个资源是唯一的,资源变化都会导致 ETag 变化。服务器根据浏览器上发送的 If-None-Match
值来判断是否缓存。
与 Last-Modified 不一样的是,当服务器返回 304 Not Modified 的响应时,由于 ETag 重新生成过,response header 中还会把这个 ETag 返回,即使这个 ETag 跟之前的没有变化。
并不是所有请求都能被缓存,无法被浏览器缓存的请求如下:
Cache-Control: no-cache
,pragma: no-cache(HTTP1.0)
,或 Cache-Control: max-age=0
等告诉浏览器不用缓存的请求;Cookie
、认证信息等决定输入内容的动态请求是不能被缓存的;HTTPS
安全加密的请求;POST
请求无法被缓存;Last-Modified/Etag
,也不包含 Cache-Control/Expires
的请求无法被缓存;本文参考
一文读懂浏览器缓存
实践这一次,彻底搞懂浏览器缓存机制
浏览器缓存缓存策略(看完就懂)