我理解的浏览器缓存策略

1,147 阅读7分钟

浏览器资源缓存策略

浏览器的缓存大致分为四个方面

  • Memory Cache(disk Cache)
  • Service Worker Cache
  • HTTP Cache
  • Push Cache

HTTP Cache

  • 浏览器在请求资源前会优先检测本地是否有命中强缓存。
  • 强缓存分expires与Cache-Control两种。
  • 如果命中强缓存则会向 memory cache或disk cache等多种缓存中去获取。
  • 如果没有命中则会向服务端去请求资源。

所以优先顺序是强缓存--->协商缓存--->源服务器获取

HTTP缓存分为强缓存与协商缓存强缓存使用expires与Cache-Control等属性来控制

强缓存

expires

expires是HTTP/1.0时期提出的,主要是由服务端设置过期时间,然后浏览器通过对比本地时间与expires来确定资源是否过期是不是需要向服务端去索取资源。

expires: Wed, 11 Sep 2019 16:12:18 GMT

缺点: expires是通过浏览器本地时间来对比的,如果人为的修改本地时间会导致资源缓存强缓存命中失败,重新去获取资源

Cache-Control

Cache-Control是HTTP/1.1作为expires全面的代替者提出的,通过对Cache-Control设置不同属性来进行资源缓存的判定

其属性值有

  • no-cache: 数据内容不能被浏览器缓存, 每次请求都重新访问服务器确认是否过期(进行协商缓存), 若有max-age, 则缓存期间不访问服务器.
  • no-store: 浏览器、服务器、代理服务器等全部不能缓存,
  • private(默认): 只能在浏览器中缓存, 只有在第一次请求的时候才访问服务器, 若有max-age, 则缓存期间不访问服务器.
  • public: 可以被任何缓存区缓存, 如: 浏览器、服务器、代理服务器等
  • max-age: 相对过期时间, 即以秒为单位的缓存时间.
  • s-max-age: 与max-age相似,两者同时存在时优先使用s-max-age,并只在代理服务器使用且只对public有效
  • no-cache, private: 打开新窗口时候重新访问服务器 , 若设置max-age, 则缓存期间不访问服务器.
  • private, 正数的max-age: 后退时候不会访问服务器
  • no-cache, 正数的max-age: 后退时会访问服务器
cache-control: max-age=3600, s-maxage=31536000

如果只设置了max-age,默认会采取public模式模式可以被全缓存的

协商缓存

协商缓存是浏览器与服务器之间的通讯,浏览器向服务器询问相关信息,确定本地文件是否过期是否需要向服务器重新获取资源,如果资源没有变动则会重定向至浏览器端并且此时的Status Code为304 Not Modified

那么协商缓存缓存是怎么实现的呢?

  • 通过Last-Modified与ETag来进行

Last-Modified

当cache-control为no-cahce时在响应头上会带上Last-ModifiedETag,同时Last-Modified是一个时间戳。

并且浏览器会在随后的请求的请求头上都携带上If-Modified-Since,这个If-Modified-Since也是一个时间戳,而这个If-Modified-Sincee也正是上次请求的资源中携带过来的Last-Modified,这个时候服务器会对比该资源的时间戳跟服务器上的资源的时间戳是否一样,如果一致则返回304,重定向让浏览器在缓存中取得,如果不一致则重新返回文件。

但是同样这样也是有弊端的

  • 如果修改了文件但是没有修改内容,这样文件的修改时间同样会发生变化,同样触发校验时则会导致触发一次资源的重新请求
  • 因为Last-Modified与If-Modified-Since都是使用时间戳来设置的,只要修改保存的时间足够快在100ms内完成了改动,则时间戳并不会变化,使得在校验时无法检验出变化,没有对新资源进行请求。

为了解决这样的问题,ETag也作为Last-Modified的加强与补充出现了

ETag

ETag是服务器给资源生成的标识字符串,作为Last-Modified的加强与补充,他跟Last-Modified工作原理很相似,在资源的响应头中生成ETag,在后续请求的请求头中会生成If-None-Match,通过比对两者的差异,如果Last-Modified与ETag同时存在时,对ETag进行优先判定

同样ETag也是有其弊端,不同于文件修改系统默认会记录其修改的时间,ETag的生成是需要额外的开销,大量的资源生成会影响性能,如果没有特殊要求使用Last-Modified已经可以满足大多数需求了。

(对ETag与Last-Modified的补充)

Nginx下配置Last-Modified时(ETag是可配置的) Apache默认是两者都会返回

那么问题来了ETag作为Last-Modified的强化与补充如果只有ETag是否也会触发交互? 结果是:ETag可以单独使用,与服务端进行资源判定

HTTP缓存指南

那对于这些资源设置应该是怎么样的呢?我们这里参考一下Chrome给出的流程图

首先我们需要确认资源是不是需要复用?如果不复用则将cache-control设置为no-store,如果需要复用则将cache-control设置为no-cache,随后我们根据资源是不是需要被代理服务器缓存来设置public或者private,最后再设置max-age;最后在设置Last-ModifiedETag等属性。

MemoryCache与DiskCache

MemoryCache:在服务器内存空余的时候优先使用Memory Cache随后才会考虑使用disk Cache, 因为Memory Cache读取速度是最快的同时也是最短命的在浏览器关闭该页面时就会销毁资源,disk Cache读取速度比Memory Cache要慢但是由于它是存在硬盘中的所以它的存在时间是最长的也是最稳定的。

如图在我关闭原有页面后重新打开该页面,没有从memory cache中去获取的资源,都是从disk cache中去获取的

Service Worker Cache

上面介绍了httpcache又介绍了memoryCache,现在来介绍一下一个更陌生的service worker cache。 我们书写的js代码通常是在主线程运行,可以访问DOM与Windows全局变量,Service Worker与Web Worker则是独立于主线程的JavaScript线层,因为他被设计成完全异步的,所以她不会阻塞页面渲染也不会阻塞JavaScript主流程的执行所以我们可以用它去缓存离线资源,推送消息等。 同时Service Worker对协议也是有要求的,必须是https,但是这个对于我们本地调试或者开发其实是不友好的,不过还算Service Worker还算是人性化,可以再localhost跟127.0.0.1环境下运行,同时github也可以执行相关的代码。 这里我们准备一个demo

我们启一个服务器(这里我推荐使用live-server) 页面第一次打开时Application下的cache Storage是没有任何文件的,但是在看到控制台中显示

ServiceWorker registration successful with scope:  http://127.0.0.1:8080/

再看cache Storage,则会对出一条数据,他就是用于存储本地文件, 我们重新请求页面会发现serviceWorker.html文件是从serviceworker中获取的

Push Cache

【持续更新中....】

参考文献 借助Service Worker和cacheStorage缓存及离线开发 前端性能优化原理与实践