作为一个前端开发者,每天都在和浏览器打交道,对于浏览器缓存,相信都不会陌生,同时它也是我们日常开发中存在的一个非常重要的优化手段,无论在节省带宽、提高加载和渲染速度、减少网络阻塞,以及提高用户体验上,都有重要的作用。
对于浏览器缓存,主要针对的是前端的静态资源,最好的效果是,在发起请求后,拉取相应的静态资源,并保存在本地。如果服务器的静态资源没有更新,那么在下次请求的时候,直接从本地读取资源即可;如果服务器的静态资源已经更新,那么我们再次请求的时候,就要去服务器拉取新的资源,并保存到本地。这样就大大减少了请求次数,提高网站性能。这就用到浏览器的缓存策略了。
总之使用浏览器缓存,有以下优点:
这是我们输入网址后,最开始的一个缓存;通常我们输入一个网址,它包含了域名和端口可以指定唯一的IP地址,然后建立连接进行通信,而域名查找IP地址的过程就是DNS解析。
www.baidu.com (域名) - DNS解析 -> 180.76.76.76 (IP地址)
这个过程会对网络请求带来一定的损耗,所以浏览器在第一次获取到IP地址后,会将其缓存起来。下次相同域名再次发起请求时,浏览器会先查找本地缓存,如果缓存有效,则会直接返回该IP地址,否则会继续开始寻址之旅。
关于寻址过程请看我之前的文章 浏览器从输入URL到页面渲染加载的过程(浏览器知识体系整理)

备注:
memory cache =》浏览器内存缓存
disk cache =》 硬盘缓存
下面将从 缓存位置 和 缓存策略 两个角度介绍浏览器缓存。
浏览器中的缓存位置,按优先级从高到低排列分别是:
Service Worker 借鉴了 Web Worker的 思路,即让 JS 运行在主线程之外,是一个独立的线程,它的特点是:
主要功能就是拦截请求和缓存资源;
独立于主线程之外,是一个独立的线程,不会造成阻塞;
由于它脱离了浏览器的窗体,因此无法直接访问DOM对象。
提供了很多新的能力,比如离线缓存、消息推送和网络代理等功能。其中的离线缓存就是 Service Worker Cache。
处于安全考虑, Service Worker 采用 HTTPS 协议;
Service Worker 同时也是 PWA 的重要实现机制,关于PWA ,可以自行百度。
读取速度快,但容量小,且时效性短;不受开发者控制,也不受HTTP协议头的约束。一旦浏览器 tab 页关闭,memory cache 就将被清空,再次重新打开相同页面时不再出现from memory cache的情况。否定的,当 HTTP 头设置了 Cache-Control: no-store 的时候或者浏览器开发者工具 network 设置了 Disabled cache 禁用缓存后,就无法把资源存入内存了,其实也无法存入硬盘。当从 memory cache 中查找缓存的时候,不仅仅会去匹配资源的 URL,还会看其 Content-type 是否相同。比内存缓存慢的,但是优势是存储容量大和存储时间长等优点。HTTP 响应头的各类字段进行判定资源的缓存规则,比如是否可以缓存,什么时候过期,过期之后是否需要重新发起请求呢?memory cache 和 disk cache 对比
| - | memory cache(内存缓存) | disk cache(磁盘缓存) |
|---|---|---|
| 相同点 | 都是浏览器的本地缓存机制 | 都是浏览器的本地缓存机制 |
| 不相同点 | 退出进程时会被清除 | 退出进程时不会被清除 |
| 存储资源 | 例如图像、样式表、脚本等保存在内存中 | 一般保存非脚本,如css等 |
注意:

根据 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:与public相反,缓存服务器会对特定用户提供资源缓存,对于其他用户发送来的请求,代理服务器不会返回缓存。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 跟之前的没有变化。
tab 页并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话),其次才是 disk cache。Network 面板下设置了 Disabled cache 禁用缓存后,浏览器将不会从 memory cache 或者 disk cache 中读取缓存,而是直接发起网络请求。所以这个按钮注意不要打钩。并不是所有请求都能被缓存,无法被浏览器缓存的请求如下:
Cache-Control: no-cache ,pragma: no-cache(HTTP1.0),或 Cache-Control: max-age=0 等告诉浏览器不用缓存的请求;Cookie、认证信息等决定输入内容的动态请求是不能被缓存的;HTTPS 安全加密的请求;POST 请求无法被缓存;Last-Modified/Etag,也不包含 Cache-Control/Expires 的请求无法被缓存;通过前端代码是不能控制缓存的,缓存是后端或者部署的同学来控制的,但是前端同学应该知道那些内容要被缓存,和后端或者部署的同学配合来打!