缓存命中顺序
顺序从上往下,命中即返回缓存的内容。
- Service Worker
- Memory Cache
- Disk Cache(HTTP Cache)
- 网络请求
Memory Cache
内存存储空间较小,一般在Tab关闭后失效。若内存缓存的数据过多,之前的缓存可能失效。
几乎所有的请求资源 都能进入 memory cache,这里主要:rel="preload"与 rel="preloader"
<!-- 预加载关键字体 --><link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin><!-- 预加载首屏图片 --><link rel="preload" href="hero.jpg" as="image">在浏览器打开网页的过程中,会先请求HTML然后解析。解析过程会从上往下扫描,遇到一个资源(js/css)就加载并解析,然后再是下一个。在解析的时候网络是空闲的,如果能一边解析一边加载下一批资源,这样就可以充分利用资源并加速加载过程。
<link rel="preload" /> 需要放在<head>中合理的源前。
rel还有其他很多取值。
Disk Cache(HTTP Cache)
disk cache 会严格根据 HTTP 头信息中的各类字段来判定哪些资源可以缓存,哪些资源不可以缓存;哪些资源是仍然可用的,哪些资源是过时需要重新请求的。当命中缓存之后,浏览器会从硬盘中读取资源,虽然比起从内存中读取慢了一些,但比起网络请求还是快了不少的。绝大部分的缓存都来自这里
强缓存
强制缓存的含义是,当客户端请求后,会先访问缓存数据库看缓存是否存在。如果存在则直接返回;不存在则请求真的服务器,响应后再写入缓存数据库。
强制缓存直接减少请求数,是提升最大的缓存策略。 它的优化覆盖了文章开头提到过的请求数据的全部三个步骤。如果考虑使用缓存来优化网页性能的话,强制缓存应该是首先被考虑的。
可以造成强制缓存的字段是 Cache-control 和 Expires。
Expires: Thu, 10 Nov 2017 08:45:11 GMT(HTTP/1.0)
HTTP 1.0 的字段,表示缓存到期时间,是一个绝对的时间 (当前时间+缓存时间)。
缺点:
- 由于是绝对时间
- 用户可能会将客户端本地的时间进行修改
- 时差或者误差等因素。
- 格式复杂
Cache-control: max-age=2592000(HTTP/1.1中,新增)优先级高于Expires
max-age 是相对时间,Cache-control中除了max-age外还有一些其他字段,完整见https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Reference/Headers/Cache-Control
must-revalidate:如果超过了max-age的时间,浏览器必须向服务器发送请求,验证资源是否还有效。no-cache:虽然字面意思是“不要缓存”,但实际上还是要求客户端缓存内容的,只是是否使用这个内容由后续的对比来决定。no-store: 真正意义上的“不要缓存”。所有内容都不走缓存,包括强制和对比。public:所有的内容都可以被缓存 (包括客户端和代理服务器, 如 CDN)private:所有的内容只有客户端才可以缓存,代理服务器不能缓存。默认值。
协商缓存
当强制缓存失效(超过规定时间)时,就需要使用对比缓存,由服务器决定缓存内容是否失效。
浏览器向服务端请求,服务端判断缓存若未失效,则返回HTTP 状态码 304 表示继续使用,浏览器直接使用本地的换,否则使用返回的响应。
没强缓存只有协商缓存的资源,每次都会向服务器发请求,但每次响应可能是‘HTTP 状态码 304’也可能是新的资源。优点在于节省响应内容。
在实际中都是强缓存与协商缓存一起使用的。
协商缓存相关请求头:
Last-Modified & If-Modified-Since
- 服务器通过
Last-Modified字段告知客户端,资源最后一次被修改的时间,例如Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT - 浏览器将这个值和内容一起记录在缓存数据库中。
- 下一次请求相同资源时时,浏览器从自己的缓存中找出“不确定是否过期的”缓存。因此在请求头中将上次的
Last-Modified的值写入到请求头的If-Modified-Since字段 - 服务器会将
If-Modified-Since的值与Last-Modified字段进行对比。如果相等,则表示未修改,响应 304;反之,则表示修改了,响应 200 状态码,并返回数据。
但是他还是有一定缺陷的:
- 如果资源更新的速度是秒以下单位,那么该缓存是不能被使用的,因为它的时间单位最低是秒。
- 如果文件是通过服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能没有变化,所以起不到缓存的作用。
Etag & If-None-Match
为了解决上述问题,出现了一组新的字段 Etag 和 If-None-Match
Etag 存储的是文件的特殊标识(一般都是 hash 生成的),服务器存储着文件的 Etag 字段。之后的流程和 Last-Modified 一致,只是 Last-Modified 字段和它所表示的更新时间改变成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 变成了 If-None-Match。服务器同样进行比较,命中返回 304, 不命中返回新资源和 200。
Etag 的优先级高于 Last-Modified
Service Worker
上述的缓存策略以及缓存/读取/失效的动作都是由浏览器内部判断。
Service Worker中可以通过cache 来实现缓存。在devTools中的Application -> Cache Storage 可以找到。
场景:
- 缓存静态资源
- 特殊场景的动态内容:文章等
- 优化首屏加载速度:立即返回缓存(如有),同时后台更新最新数据到缓存,下次进入即为最新,或者通过postMessage通知界面更新
缓存失效:
- 手动调用 API
cache.delete(resource) - 容量超过限制
- 浏览器全部清空
Service Worker的特点:
- 独立于主JavaScript线程
- 设计完全异步,大量使用Promise
- 不能访问DOM,不能使用XHR和localStorage
- Service Worker只能由HTTPS承载(出于安全考虑)
使用步骤
- 通过
navigator.serviceWorker.register('/sw.js')注册Service Wroker sw.js中通过cache对象管理缓存- 可以在
self.addEventListener('install', event => { .. })事件中主动缓存一些资源
- 可以在
sw.js中通过self.addEventListener('fetch', event => { .. })事件拦截请求,判断缓存是否存在且有效,若有效则返回缓存内容,若无效则放行请求,并在请求成功后加入缓存。