写在前面:
前段时间公司同事分享了前端缓存有关的知识,作为一个还没毕业的实习生,听得我一头雾水,遂在闲暇之际,自己捋一捋。
缓存是性能优化中非常重要的一环,缓存的意义就在于减少请求,更多地使用本地的资源,给用户更好的体验的同时,也减轻服务器压力。接下里从三个部分来说明一下前端的缓存。
1.强缓存
2.协商缓存
3.缓存位置
强缓存
我们在请求资源的时候,首先检查强缓存,如果命中强缓存,那么Http请求都不需要发送了,直接使用缓存。少了一次请求(DNS解析,三次握手啥的都不需要了)
值得注意的是,强缓存在Http/1.0和Http/1.1中,用来判断的字段是不一样的。在Http/1.0中使用的是Expires,而Http/1.1中使用的是Cache-Control。
Expires
从字面理解,Expires即过期时间,存在于服务器返回的响应头中,告诉浏览器在这个时间之前可以从缓存中获取数据,不需要发起Http请求,比如这样
Expires: Wed, 22 Nov 2019 08:41:00 GMT
但是这样会有一个问题。服务器的时间和浏览器的时间可能并不一致,那么服务器返回的这个过期时间可能就不是准确的。因此后来Http1.1中采用了新的机制来替换Expires
Cache-Control
在Http/1.1中,采用了Cache-Control来控制强缓存,它并不像Http/1.0中使用一个特定的时间,而是采用过期时长来控制缓存,对应的字段是max-age,比如这个例子
Cache-Control:max-age=3600
不仅如此,Http/1.1中还可以设置其他属性来完成更多场景的缓存判断。
max-age //(单位秒)
s-maxage // 只在代理服务器中生效,比如 CDN 缓存
private // 只有客户端可以缓存
public // 中间节点也可以缓存,比如CDN
no-store // 非常粗暴,不缓存
no-cache // 跳过当前的强缓存,发送HTTP请求,即直接进入协商缓存
must-revalidate // 告诉浏览器、缓存服务器,本地副本过期前,可以使用本地副本;本地副本一旦过期,必须去源服务器进行有效性校验(本地过期前不会去发请求校验)。
当没有命中强缓存,或者强缓的资源缓冲时间超时了,也就是强缓存失效了,那么就进去了第二级屏障----协商缓存。
协商缓存
强缓存失效后,浏览器在请求头携带“相应的标签”来向服务器发送请求。由服务器根据这个tag,来决定是否使用缓存,这就是协商缓存。
“相应的标签”分为两种: 第一种是Last-Modified,第二种是 ETag。这两种各有优劣。
ETag
ETag 是服务器根据当前文件的内容,给文件生成的唯一标识,只要里面的内容有改动,这个值就会变。服务器通过响应头把这个值给浏览器。浏览器接收到后,如果再次请求,会在请求头中携带If-None-Match字段,这个字段的值也就是服务器传来的最后修改时间。服务器拿到请求头中的If-None-Match的字段后,其实会和这个服务器中该资源的最后修改时间对比:
-
如果请求头中的这个值小于最后修改时间,说明是时候更新了。返回新的资源,跟常规的HTTP请求响应的流程一样。
-
否则返回304,告诉浏览器直接用缓存。如下图(ETag和If-None-Match)
Last-Modified
Last-Modified即最后修改时间。第一次向服务器请求了某个资源后,服务器会在响应头返回If-Modified-Since字段,也就是这个资源的最后修改时间。之后再次请求,会在请求头中使用Last-Modified这个字段携带If-Modified-Since的值去和服务器做比较,如果发生变换,则说明这个资源更新了,返回新的资源。反之,没有发生变换,则返回304,高速浏览器直接使用缓存就可以。
两者对比
- 在精准度上,ETag优于Last-Modified。优于 ETag 是按照内容给资源上标识,因此能准确感知资源的变化。而 Last-Modified 就不一样了,它在一些特殊的情况并不能准确感知资源变化,主要有两种情况:
-
编辑了资源文件,但是文件内容并没有更改,这样也会造成缓存失效。
-
Last-Modified 能够感知的单位时间是秒,如果文件在 1 秒内改变了多次,那么这时候的 Last-Modified 并没有体现出修改了。
2.在性能上,Last-Modified优于ETag,也很简单理解,Last-Modified仅仅只是记录一个时间点,而 ETag需要根据文件的具体内容生成哈希值。
另外,如果两种方式都支持的话,服务器会优先考虑ETag
。
缓存位置
浏览器的缓存位置一共分为四种,按优先级从高到低排列分别是:
Service Worker
Memory Cache
Disk Cache
Push Cache
Service Worker
Service Worker 借鉴了 Web Worker的 思路,即让 JS 运行在主线程之外,由于它脱离了浏览器的窗体,因此无法直接访问DOM
。虽然如此,但它仍然能帮助我们完成很多有用的功能,比如离线缓存
、消息推送
和网络代理
等功能。其中的离线缓存
就是 Service Worker Cache。
Service Worker 同时也是 PWA 的重要实现机制,关于它的细节和特性,我们将会在后面的 PWA 的分享中详细介绍。
Memory Cache 和 Disk Cache
Memory Cache指的是内存缓存,从效率上讲它是最快的。但是从存活时间来讲又是最短的,当渲染进程结束后,内存缓存也就不存在了。
Disk Cache就是存储在磁盘中的缓存,从存取效率上讲是比内存缓存慢的,但是他的优势在于存储容量和存储时长。稍微有些计算机基础的应该很好理解,就不展开了。
好,现在问题来了,既然两者各有优劣,那浏览器如何决定将资源放进内存还是硬盘呢?主要策略如下:
- 比较大的JS、CSS文件会直接被丢进磁盘,反之丢进内存
- 内存使用率比较高的时候,文件优先进入磁盘
Push Cache
即推送缓存,这是浏览器缓存的最后一道防线。它是 HTTP/2
中的内容,虽然现在应用的并不广泛,但随着 HTTP/2 的推广,它的应用越来越广泛。
参考链接: https://juejin.im/post/5df5bcea6fb9a016091def69