简析浏览器缓存及实践

728 阅读5分钟

浏览器缓存是一个老生常谈的话题,也是现阶段前端面试必问高频问题之一。

这篇文章主要总结下我对缓存的认识,有什么不足之处欢迎指正。

强缓存和协商缓存

我们一般把浏览器缓存分为强缓存和协商缓存。

强缓存

强缓存,顾名思义,指的就是每次请求都走或者不走缓存,不和服务端交互

走或不走缓存主要通过expirescache-control字段来控制。

expire是一个时间戳,其实本质上它就是一个过期时间,指的是当浏览器时间(本地时间)在这个时间戳之前都就会命中缓存,否则就去请求服务器。

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

expire容易出先一个问题,一旦浏览器端和服务器端的时间不相同,容易出现一些不可预测的问题

所以现代高级浏览器提出了一个叫cache-control的属性,来替换expirescache-control主要通过max-age来控制缓存过期时间

cache-control: max-age=31536000

max-age后面跟着的是一个时间段,指的是在这个请求发出后的max-age时间段内再次请求都会命中缓存,而不去和服务器交互,知道超出这个时间段。

现代的高级浏览器都支持cache-control属性,但是一般而言,为了向下兼容我们会将expirescache-control同时设置。当然,两者同时设置时cache-control的优先级要高于expires

此外,catch-control除了max-age外,还有其它属性,在这边简单介绍一下

  • no-cache: 是指不直接取缓存,而是先去向服务端确认该资源是否过期,若没过期则直接取缓存(即直接走下面说的协商缓存的策略)。
  • no-store: 是指就不使用缓存,每次请求都会去服务器请求新数据。

协商缓存

协商缓存和强缓存的区别就是,强缓存只要没过期,就会去取缓存;而协商缓存则是每次请求都会去询问服务器文件是否修改过,如果服务器提示为未修改,则直接走本地缓存,否则去请求服务器的数据

如果服务端提示缓存资源未修改(Not Modified),资源会被重定向到浏览器缓存,这种情况下网络请求对应的状态码是 304,如下图所示。

协商缓存通过Last-ModifiedETag这两个字段来控制,以上连个字段的信息可以在Response Headers 里看到。

Last-Modified: Fri, 25 Oct 2019 03:36:42 GMT

Last-Modified是一个时间戳,一旦设置里协商缓存,当首次请求的时候,在响应头上都会带上这个参数,并且当再次请求服务器时,会把这个时间戳带到请求头里的If-Modified-Since字段里

If-Modified-Since: Fri, 25 Oct 2019 03:36:42 GMT

服务器在就收到这个时间戳后会和资源在服务器上到最后修改时间对比,若一致则直接走缓存,且状态码返回403。若不一致则返回一个完整的响应内容,并在 Response Headers 中添加新的 Last-Modified 值。

但是,Last-Modified有两个明显的缺点

  • 如果我们保存了的资源但是我们没有修改资源内容,这时候我们想要的是去取缓存的值,但是因为最新修改时间发生变化,所以我们会去服务器取新的资源。
  • 如果我们修改时间过快(比如100ms), If-Modified-Since是检测不道德,它只能检查到以秒为最小计量单位的时间差,所以它是感知不到这个改动的——该重新请求的时候,反而没有重新请求了。

这两个场景其实指向了同一个 问题——服务器并没有正确感知文件的变化。为了解决这样的问题,ETag 作为 Last-Modified补充出现了。

ETagLast-Modified 的机制类似,在首次请求的时候在响应头上添加一个ETag字段。这个 ETag其实相当于一个唯一标识符,这个标识字符串是基于文件内容编码的,只要文件内容不同,它们对应的 Etag 就是不同的

ETag: "5db29868-16176b"

当再次请求时,会在请求头上添加If-None-Match字段,服务器就收到这个唯一标识符和服务器上的资源生成的唯一标识符对比,若相同,则取缓存,否则去服务器取新资源。

If-None-Match: W/"5db29868-16176b"

Etag 的生成过程需要服务器额外付出开销,会影响服务端的性能,这是它的弊端。因此启用 Etag 需要我们审时度势。正如我们刚刚所提到的——Etag 并不能替代 Last-Modified,它只能作为 Last-Modified 的补充和强化存在。Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准

实际应用

我们使用缓存的目的是尽可能的减少http请求,以提高页面相应速度。所以最好的策略就是尽可能的使用强缓存;同时在发布新版本的时候使缓存失效,重新去服务器请求资源

现阶段,单页面应用大行其道,对于单页面应用来说,怎么在更新版本时,让缓存失效呢?

我们可以给对应的资源添加一个hash值,将文件路径修改,这样每次版本更新的时候就会去请求新的资源。

webpack对于文件设置hash值提供了很好的支持。

entry:{
    main: path.join(__dirname,'./main.js'),
    vendor: ['react', 'antd']
},
output:{
    path:path.join(__dirname,'./dist'),
    publicPath: '/dist/',
    filname: 'bundle.[chunkhash].js'
}

所以综上所述,对单页面应用

  • js、css、静态图片:使用强缓存,并设置hash
  • html:使用协商缓存.