增量更新是目前大部分团队采用的缓存更新方案,能让用户在无感知的情况获取最新内容。具体实现方式通常是(一般我们通过构建工具来实现,比如 webpack):
构建产出文件 hash(如:index.d94f83fac22c203b788c.css)
更新 html 文件里静态资源的引用 URL
由于其他资源是由 html 文件直接或间接引用才可以被加载,如果 html 里的静态资源 URL 更新了,那请求的肯定就是最新资源了。那我们需要考虑的就是如何确保加载的 html 是最新的了,其他的静态资源就充分利用浏览器缓存以减少网络请求提高 web 性能。浏览器缓存实质就是 HTTP 缓存,而 HTTP 缓存分为强缓存和协商缓存。
强缓存
强缓存不会发送请求,直接从浏览器加载资源。是否命中强缓存根据 HTTP Response 头部 Expires、Cache-Control(max-age)来判断。
Expires 通过返回一个过期时间来判断是否过期,在此时间之前浏览器直接从缓存加载资源。但其缺点是返回的过期时间为服务器时间,而比较是同客户端时间比较,如果服务端和客户端存在时间误差就不准了。
max-age 返回的时间过期时间跨度,比如 max-age=3600 告诉浏览器接下来的 1 小时内使用缓存。这样就解决了 Expires 时间误差导致的问题。
强缓存命中优先判断 max-age,max-age 优先级大于 Expires,判断流程如下:
协商缓存
Etag 和 If-None-Match Etag 的值是根据一定算法生成的字符串,用以判断文件是否更改,类似于文件 hash。在第一次访问时,服务会返回该文件的 Etag 值,在之后的访问通过在请求头增加 If-None-Match 参数,把 Etag 的值带过去,服务器通过比较 Etag 的值来判断是否需要重新返回最新资源(200 or 304)。
Last-Modified 和 If-Modified-Since Last-Modified 为该资源文件的最后修改时间。同样在第一次返回,之后通过 If-Modified-Since 带过去,服务器通过比较值来判断是否需要返回最新资源。
如果 If-None-Match 和 If-Modified-Since 都有,则必须一次性都发给服务器,没有优先级,实际中比较 ETag 就够了。如果请求头里没有,则每次都会返回最新的资源 200
缓存判断流程
为了确保 html 每次都是最新的,我们这里不能使用强缓存。可以在服务器(nginx、tomact)设置访问 html 文件时 Cache-Control 为 no-cache。(这个需要在服务器配合设置,前端是处理不了的)