1 浏览器缓存浏览器缓存机制有四个方面,它们按照获取资源时请求的优先级依次排列如下:1.Memory Cache2.Service Worker Cache3.HTTP Cache4.Push Cache1.1 Memory CacheMemoryCache,是指存在内存中的缓存。从优先级上来说,它是浏览器最先尝试去命中的一种缓存。从效率上来说,它是响应速度最快的一种缓存。不过当页面关闭时,内存里的数据也就没有了。资源存不存内存,浏览器秉承的是“节约原则”。我们发现,Base64 格式的图片,几乎永远可以被塞进 memory cache,这可以视作浏览器为节省渲染开销的“自保行为”;此外,体积不大的 JS、CSS 文件,也有较大地被写入内存的几率——相比之下,较大的 JS、CSS 文件就没有这个待遇了,内存资源是有限的,它们往往被直接甩进磁盘。1.2 Service Worker CacheService Worker 是一种独立于主线程之外的 Javascript 线程。它可以帮我们实现离线缓存、消息推送和网络代理等功能。通常我们如果要使用 Service Worker 基本就是以下几个步骤:首先我们需要在页面的 JavaScript 主线程中注册 Service Worker。注册成功后后台开始安装步骤, 通常在安装的过程中需要缓存一些静态资源。安装成功后开始激活 Service Worker激活成功后 Service Worker 可以控制页面了(监听 fetch 和 message 事件),但是只针对在成功注册了 Service Worker 后打开的页面。在页面发起 http 请求时,service worker 可以通过 fetch 事件拦截请求,并且给出自己的响应。页面和 serviceWorker 之间可以通过 posetMessage() 方法发送消息,发送的消息可以通过 message 事件接收到。Service Worker 必须以 https 协议为前提。1.3 HTTP 缓存HTTP 缓存分为强缓存和协商缓存。优先级较高的是强缓存,在命中强缓存失败的情况下,才会走协商缓存。1.3.1 强缓存强缓存指的是向浏览器缓存查找该请求的结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程强缓存是利用http响应头中的 Expires 和 Cache-Control 两个字段来控制的。1.3.1.1 Expires实现强缓存,过去我们一直用 expires。在服务器的响应头里,会将过期时间写入 expires 字段:那么,当我们试图再次向服务器请求资源时,浏览器就会先对比本地时间和 expires 的时间,如果本地时间小于 expires 设定的过期时间,就直接去缓存中取这个资源。不过expires依赖于本地时间,如果服务端和客户端的时间设置不同,那么expires 将无法达到我们的预期。1.3.1.2 Cache-Control考虑到 expires 的局限性,HTTP1.1 新增了 Cache-Control 字段来完成 expires 的任务。当 Cache-Control 与 expires 同时出现时,我们以 Cache-Control 为准。Cache-Control 包含以下几个值:(1)max-agecache-control: max-age=31536000max-age 会等于一个时间长度(以秒为单位)。在本例中,max-age 是 31536000 秒,它意味着该资源在 31536000 秒以内都是有效的,完美地规避了时间戳带来的潜在问题。在代理服务器中,我们使用 s-maxage 来执行 max-age 的功能。(2)public 与 private如果我们为资源设置了 public,那么它既可以被浏览器缓存,也可以被代理服务器缓存(也就是多个用户可以共享这个缓存);如果我们设置了 private,则该资源只能被浏览器缓存。private 为默认值。但多数情况下,public 并不需要我们手动设置,因为设置了 max-age 就表示响应是可以缓存的。(3)no-store 与 no-cache如果我们为资源设置了 no-cache,浏览器会对响应进行缓存,但是需要到服务器去确认这个缓存是否能用。即走我们下文即将讲解的协商缓存的路线。如果设置了 no-store ,所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存1.3.2 协商缓存协商缓存指的是强制缓存失效后,浏览器向服务器询问缓存的相关信息,进而判断是重新发起请求还是从本地拿缓存的过程。如果服务端提示缓存资源未改动(Not Modified),资源会被重定向到浏览器缓存,这种情况下网络请求对应的状态码是 304(如下图)。同样,协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,控制协商缓存的字段分别有:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高。1.3.2.1 Last-Modified / If-Modified-Since如果我们启用了协商缓存,Last-Modified 会在首次请求时随着响应头返回:Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT随后我们每次请求时,会带上一个叫 If-Modified-Since 的时间戳字段,它的值正是上一次 response 返回给它的 last-modified 值:If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT服务器接收到这个时间戳后,会比对该时间戳和资源在服务器上的最后修改时间是否一致,从而判断资源是否发生了变化。如果发生了变化,就会返回一个完整的响应内容,并在响应头中添加新的 Last-Modified 值;否则,返回如上图的 304 响应,响应头不会再添加 Last-Modified 字段。1.3.2.2 Etag / If-None-MatchEtag 是由服务器为每个资源生成的唯一的标识字符串,这个标识字符串是基于文件内容编码的,只要文件内容不同,它们对应的 Etag 就是不同的。当首次请求时,我们会在响应头里获取到一个最初的标识符字符串:ETag: W/“2a3b-1602480f459"那么下一次请求时,请求头里就会带上一个值相同的、名为 if-None-Match 的字符串供服务端比对:If-None-Match: W/“2a3b-1602480f459"不过 Etag 的生成过程需要服务器额外付出开销,会影响服务端的性能。1.3.3 HTTP 缓存决策指南根据上文所说的 HTTP 缓存知识点,我们在面对一个具体的缓存需求时,可以根据下图的路线来决策:当我们的资源内容不可复用时,直接为 Cache-Control 设置 no-store,拒绝一切形式的缓存;否则考虑是否每次都需要向服务器进行缓存有效确认,如果需要,那么设 Cache-Control 的值为 no-cache;否则考虑该资源是否可以被代理服务器缓存,根据其结果决定是设置为 private 还是 public;然后考虑该资源的过期时间,设置对应的 max-age 和 s-maxage 值;最后,配置协商缓存需要用到的 Etag、Last-Modified 等参数。1.4 Push CachePush Cache 是指 HTTP2 在 server push 阶段存在的缓存。2 服务器缓存2.1 CDNCDN (Content Delivery Network,即内容分发网络)指的是一组分布在各个地区的服务器。这些服务器存储着数据的副本,因此服务器可以根据哪些服务器与用户距离最近,来满足数据的请求。 CDN 提供快速服务,较少受高流量影响。CDN 的核心点有两个,一个是缓存,一个是回源。“缓存”就是说我们把资源 copy 一份到 CDN 服务器上这个过程,“回源”就是说 CDN 发现自己没有这个资源(一般是缓存的数据过期了),转头向根服务器(或者它的上层服务器)去要这个资源的过程。CDN 往往被用来存放静态资源,就是像 JS、CSS、图片等不需要业务服务器进行计算即得的资源。3 HTML5缓存3.1 Web StorageWeb Storage 是 HTML5 专门为浏览器存储而提供的数据存储机制。存储容量可以达到 5-10M 之间。它又分为 Local Storage 与 Session Storage。3.1.1 Local Storage 与 Session Storage 的区别两者的区别在于生命周期与作用域的不同。生命周期:存储在Local Storage的数据是永远不会过期的,使其消失的唯一办法是手动删除;而 Session Storage 是临时性的本地存储,当会话结束(页面被关闭)时,存储内容也随之被释放。作用域:Local Storage、Session Storage 和 Cookie 都遵循同源策略。但 Session Storage 特别的一点在于,即便是相同域名下的两个页面,只要它们不在同一个浏览器窗口中打开,那么它们的 Session Storage 内容便无法共享。3.1.2 Web Storage 核心 API 使用示例(1)存储数据localStorage.setItem(‘user_name’, ‘xiuyan’)sessionStorage.setItem(‘key’, ‘value’);Web Storage只能存字符串。(2)读取数据localStorage.getItem(‘user_name’)var data = sessionStorage.getItem(‘key’);(3)删除某一键名对应的数据localStorage.removeItem(‘user_name’)sessionStorage.removeItem(‘key’);(4)清除所有数据localStorage.clear()sessionStorage.clear();3.1.3 应用场景Local Storage 的特点之一是持久,有时我们更倾向于用它来存储一些内容稳定的资源。比如图片内容丰富的电商网站会用它来存储 Base64 格式的图片字符串,有的网站还会用它存储一些不经常更新的 CSS、JS 等静态资源。Session Storage 更适合用来存储生命周期和它同步的会话级别的信息。这些信息只适用于当前会话,当你开启新的会话时,它也需要相应的更新或释放。比如微博的 Session Storage 就主要是存储你本次会话的浏览足迹。3.2 IndexDBIndexDB 是一个运行在浏览器上的非关系型数据库。4 参考文章前端性能优化原理与实践Service Worker 生命周期Service Worker初体验彻底理解浏览器的缓存机制