浏览器缓存一直是个老生常谈的话题,也是面试官常常用来鉴别面试者的利器,作为前端来讲这块知识是属于必须掌握的,再者利用好缓存也是做性能优化的有效方法。本文将从缓存原因、缓存读写顺序,缓存位置以及缓存策略这几个角度介绍浏览器缓存,并且最后给出实践的应用举例。
为什么要缓存
很多同学知道缓存的位置和字段,知道怎么用,但是你有没有想过为什么我们的页面需要浏览器缓存呢?
缓存读写顺序
当浏览器对一个资源(比如一个外链的 a.js)进行请求的时候会发生什么?请从缓存的角度大概说下:
1.调用 Service Worker 的 fetch 事件获取资源;
2.查看 memory cache;
3.查看 disk cache;这里又细分:
4.发送网络请求,等待网络响应;
5.把响应内容存入 disk cache (如果请求头信息配置可以存的话);
6.把响应内容的引用存入 memory cache (无视请求头信息的配置,除了 no-store 之外);
7.把响应内容存入 Service Worker 的 Cache Storage (如果 Service Worker 的脚本调用了 cache.put());
上面这一系列过程其实是浏览器查找缓存和把资源存入缓存的执行流程。这其中出现了很多专业词汇,让人看了一脸懵逼,下面将从缓存位置和缓存策略两个角度简要介绍浏览器的缓存。
缓存位置
从浏览器开发者工具的 Network 面板下某个请求的 Size 中可以看到当前请求资源的大小以及来源,从这些来源我们就知道该资源到底是从 memory cache中读取的呢,还是从 disk cache 中读取的,亦或者是服务器返回的。而这些就是缓存位置:
Service Worker
是一个注册在指定源和路径下的事件驱动 worker;特点是:
说了这么多特点,那它和缓存有啥关系?其实它有一个功能就是离线缓存:Service Worker Cache;区别于浏览器内部的 memory cache 和 disk cache,它允许我们自己去操控缓存,具体操作过程可以参看 Using_Service_Workers;通过 Service Worker 设置的缓存会出现在浏览器开发者工具 Application 面板下的 Cache Storage 中。
memory cache
是浏览器内存中的缓存,相比于 disk cache 它的特点是读取速度快,但容量小,且时效性短,一旦浏览器 tab 页关闭,memory cache 就将被清空。memory cache 会自动缓存所有资源嘛?答案肯定是否定的,当 HTTP 头设置了 Cache-Control: no-store 的时候或者浏览器设置了 Disabled cache 就无法把资源存入内存了,其实也无法存入硬盘。当从 memory cache 中查找缓存的时候,不仅仅会去匹配资源的 URL,还会看其 Content-type 是否相同。
disk cache
也叫 HTTP cache 是存在硬盘中的缓存,根据 HTTP 头部的各类字段进行判定资源的缓存规则,比如是否可以缓存,什么时候过期,过期之后需要重新发起请求吗?相比于 memory cache 的 disk cache 拥有存储空间时间长等优点,网站中的绝大多数资源都是存在 disk cache 中的。
缓存按照缓存位置划分,其实还有一个 HTTP/2 的内容 push cache,由于目前国内对 HTTP/2 应用还不广泛,且网上对 push cache 的知识还不齐全,所以本篇不打算介绍这块,感兴趣的可以阅读这篇文章:HTTP/2 push is tougher than I thought
缓存策略
根据 HTTP header 的字段又可以将缓存分成强缓存和协商缓存。强缓存可以直接从缓存中读取资源返回给浏览器而不需要向服务器发送请求,而协商缓存是当强缓存失效后(过了过期时间),浏览器需要携带缓存标识向服务器发送请求,服务器根据缓存标识决定是否使用缓存的过程。强缓存的字段有:Expires 和 Cache-Control。协商缓存的字段有:Last-Modified 和 ETag。
Expires
Expires 是 HTTP/1.0 的字段,表示缓存过期时间,它是一个 GMT 格式的时间字符串。Expires 需要在服务端配置(具体配置也根据服务器而定),浏览器会根据该过期日期与客户端时间对比,如果过期时间还没到,则会去缓存中读取该资源,如果已经到期了,则浏览器判断为该资源已经不新鲜要重新从服务端获取。由于 Expires 是一个绝对的时间,所以会局限于客户端时间的准确性,从而可能会出现浏览器判断缓存失效的问题。如下是一个 Expires 示例,是一个日期/时间:
- Expires: Wed, 21 Oct 2020 07:28:00 GMT
Cache-Control
它是 HTTP/1.1 的字段,其中的包含的值很多:
Cache-Control 的值是可以混合使用的,比如:
- Cache-Control: private, max-age=0, no-cache
当混合使用的时候它们的优先级如下图所示:
「当 Expires 和 Cache-Control 都被设置的时候,浏览器会优先考虑后者」。当强缓存失效的时候,则会进入到协商缓存阶段。具体细节是这样:浏览器从本地查找强缓存,发现失效了,然后会拿着缓存标识请求服务器,服务器拿着这个缓存标识和对应的字段进行校验资源是否被修改,如果没有被修改则此时响应状态会是 304,且不会返回请求资源,而是直接从浏览器缓存中读取。
而浏览器缓存标识可以是:Last-Modified 和 ETag:
Last-Modified
资源的最后修改时间;第一次请求的时候,响应头会返回该字段告知浏览器资源的最后一次修改时间;浏览器会将值和资源存在缓存中;再次请求该资源的时候,如果强缓存过期,则浏览器会设置请求头的 If-Modifined-Since 字段值为存储在缓存中的上次响应头 Last-Modified 的值,并且发送请求;服务器拿着 If-Modifined-Since 的值和 Last-Modified 进行对比。如果相等,表示资源未修改,响应 304;如果不相等,表示资源被修改,响应 200,且返回请求资源。如果资源更新的速度是小于 1 秒的,那么该字段将失效,因为 Last-Modified 时间是精确到秒的。所以有了 ETag。
ETag
根据资源内容生成的唯一标识,资源是否被修改的判断过程和上面的一致,只是对应的字段替换了。Last-Modified 替换成 ETag,If-Modifined-Since替换成 If-None-Match。
当 Last-Modified 和 ETag 都被设置的时候,浏览器会优先考虑后者。
浏览器的行为
缓存应用
静态资源
比如页面引入了一个 JQuery,对于页面来说这个脚本就是一个工具库,基本上是不会发生变化的,对于这种资源可以将它的缓存时间设置得长一点,比如如下这个地址的脚本:
- <script src="https://cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script>
你会看到它的响应头里设置了,max-age=2592000 直接缓存 30 天:
- cache-control: public, max-age=2592000
频繁变化的资源
对于频繁变化的资源,比如某个页面经常需要调整,那么这个页面就需要在每次请求的时候都进行验证,可以在响应头这样设置:
- cache-control: no-cache
不进行缓存
当然并不是所有请求都能被缓存,无法被浏览器缓存的请求如下:
本文已经收录于最新版的 《PyCharm中文指南》更多 PyCharm 使用技巧可前往在线文...
本文实例为大家分享了JavaScript实现点击切换验证码及校验的具体代码,供大家参...
绑定基础 几乎所有的服务容器绑定都是在 服务提供者 中完成。 在目录结构如下图 ...
哈喽..大家好 很久没有更新了,今天就来一篇最近开发用到的功能,那就是中英文切...
本文实例为大家分享了JS+CSS实现动态时钟的具体代码,供大家参考,具体内容如下 ...
笔者之前准备做一个MongoTemplate的小demo建完项目后发现pom.xml一直报错也没有j...
在jsp页面中添加base,可用相对路径: 复制代码 代码如下: % String path = requ...
python英雄联盟万图视频制作 前言 图片数据采集 图片合成视频 视频添加音效 前言...
笔者近期在公司的项目中渐渐的接触了一些比较高级的业务逻辑处理,其中比较有意...
要成为一名优秀的开发人员,需要来自多个学科的知识。 然而,在了解编程语言的基...