Skip to content

6缓存与本地存储

发起请求

DNS预解析 <link rel="dns-prefetch" href="xxxhost">

浏览器缓存机制

缓存是简单高效的优化方式之一,能显著减少网络传输所带来的损耗

对于一个请求来说,分为

  1. 发起网络请求
  2. 后端处理
  3. 浏览器响应

三个步骤。浏览器缓存可帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求

缓存位置

当依次检查缓存且都没命中时,才会去请求网络

  1. Service Worker
  2. Memory Cache
  3. Disk Cache
  4. Push Cache
  5. 网络请求

Service Worker

Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。

当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。

Disk Cache

读取稍慢,但胜在容量和存储时效上。 且及时在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。

Push cache

HTTP2 的内容,当上面三种都没命中时,它会被使用, 且缓存时间短暂,只在会话session中存在,会话结束即释放。

缓存策略

缓存策略都是通过设置HTTP Header来实现的

强缓存

可设置 Expires 和 Cache-Control。强缓存表示在缓存期间不需要请求,status code为200

  • cache-control 相对时间
    • no-store 不缓存
    • no-cache 缓存,但立即失效
    • public 可被客户端和代理服务器缓存
    • private 只被客户端缓存
  • expires 绝对时间,若修改本地时间可能造成失效

协商缓存

需求请求验证资源是否更新,协商缓存可设置

  • Etag
    • If-None-Match 本地发
    • Etag 服务端对比
  • Last-Modified 本地最后修改时间,不能感知秒内
    • If-Modified-Since 本地发服务端
    • Last-Modified 服务端响应头

思考

频繁变动的资源 需要 Cache-Control: no-cache,然后配合Etag和Last-Modified来验证资源是否失效。

代码文件 除 html外的,(webpack打包)只有修改代码才生成新文件名,那么就可设置长缓存时间

本地存储

cookie localStorage sessionStorage indexDB

特性cookielocalStoragesessionStorageindexDB
数据生命周期一般由服务器生成,可以设置过期时间除非被清理,否则一直存在页面关闭就清理除非被清理,否则一直存在
数据存储大小4K5M5M无限
与服务端通信每次都会携带在 header 中,对于请求性能影响不参与不参与不参与

从上表可以看到,cookie 已经不建议用于存储。如果没有大量数据存储需求的话,可以使用 localStorage 和 sessionStorage 。对于不怎么改变的数据尽量使用 localStorage 存储,否则可以用 sessionStorage 存储。

对于 cookie 来说,我们还需要注意安全性。

属性作用
value如果用于保存用户登录态,应该将该值加密,不能使用明文的用户标识
http-only不能通过 JS 访问 Cookie,减少 XSS 攻击
secure只能在协议为 HTTPS 的请求中携带
same-site规定浏览器不能在跨域请求中携带 Cookie,减少 CSRF 攻击

Service Worker

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。

(PWA)Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。以下是这个步骤的实现:

js
// index.js
if (navigator.serviceWorker) {
  navigator.serviceWorker
    .register('sw.js')
    .then(registration => {
      console.log('service worker 注册成功')
    })
    .catch(err => {
      console.log('service worker 注册失败')
    })
}

// sw.js
// 监听 install 事件,回调中缓存所需文件
self.addEventListener('install', e => {
  e.waitUntil(
    caches.open('my-cache').then(cache => {
      return cache.addAll(['./index.html', './index.js'])
    })
  )
})
// 拦截所有请求事件
// 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据
self.addEventListener('fetch', e => {
  e.responseWith(
    caches.match(e.request).then(resonse => {
      if (response) return response
      console.log('fetch source')
    })
  )
})

共 20 个模块,1301 篇 Markdown 文档。