前端浏览器缓存机制

·  阅读 618

前言

最近在做一个第三方平台跳转登陆的需求,由该需求可以发散到对应的知识有:浏览器缓存、单点登录等。为此我也想到了一个接下来让自己去适应的学习计划,那就是基于当前工作中项目需求里需要用到的知识去扩展学习,以此类推,下个需求又有哪些知识点,再去学习那方面相关的知识。至于学习的深度、拓展到多少相关知识点,则由该段时间自由度来决定。

缓存

缓存: 一般指的都是静态资源文件的缓存,这个一般是客户端和服务器端根据一些协商的规则,自动去完成的缓存策略(不用我们自己编写啥代码去处理)。只有API接口数据缓存,是需要前端开发自己去完成的

1.为什么我们需要浏览器缓存?

当我们访问同一个页面时,请求资源、数据都是需要一定的耗时,如果可以将一些资源缓存下来,那么从第二次访问开始,就可以减少加载时间,提高用户体验,也能减轻服务器的压力。

2.缓存的分类

Web 缓存大致可以分为:数据库缓存、服务器端缓存(代理服务器缓存、CDN 缓存)、浏览器缓存。而浏览器缓存也包含了很多内容: HTTP 缓存(强缓存和协商缓存)、数据缓存(cookie、localstorage、indexDB...) 等。

3.缓存的位置

若设置了缓存机制,则从服务器获取的信息会存储在:

  • 虚拟内存(内存条):Memory Cache(一般用于页面没有关闭,只是刷新)
  • 物理内存(硬盘): Disk Cache(页面关闭后重新打开) 换而言之,客户端/浏览器会将缓存放在内存中一份,放在硬盘中一份。内存中的缓存,页面一关闭存储的信息就被释放了;硬盘中的缓存,是持久存储,页面关闭还有,除非自己使用一些手段清除掉,如 360清除浏览器缓存。

image.png

具体处理步骤:

  • 打开网页:先读 Memory Cache,再读 Disk Cache。
  • 页面关闭再打开:查找 disk cache 中是否有匹配,如有则使用,没有则发送网络请求。
  • 普通刷新 (F5):TAB没关闭,因此 memory cache 是可用的,会被优先使用,其次才是 disk cache
  • 强制刷新 (Ctrl + F5):浏览器不使用缓存,直接从服务器获取最新资源,因此发送的请求头部均带有 Cache-control: no-cache

4.缓存处理

检测缓存 ——> 先看是否存在强缓存(强缓存存在走强缓存)——> 强缓存不存在,再看是否有协商缓存(协商存在,还是走缓存)——> 协商也不存在,则直接从服务器获取最新内容 ——> 缓存起来...(依此类推)

5.缓存的优缺点

优点:

  • 减少了不必要的数据传输,节省带宽
  • 减少服务器的负担,提升网站性能
  • 加快了客户端加载网页的速度
  • 用户体验友好 缺点:
  • 资源如果有更改但是客户端不及时更新会造成用户获取信息滞后,如果老版本有bug的话,情况会更加糟糕。

HTTP缓存

HTTP缓存分为强缓存和协商缓存。

强缓存

1.强缓存的概念和流程

强缓存(又叫强制缓存)整体流程比较简单,就是在第一次访问服务器取到数据之后,在过期时间之内不会再去重复请求。实现这个过程的核心就是如何知道当前时间是否超过了过期时间,强缓存的过期时间通过第一次访问服务器时返回的响应头获取。在 http1.0 和 http1.1 版本中通过不同的响应头字段实现。

浏览器对于强缓存的处理:根据第一次请求资源时返回的响应头来确定的

  • Expires:缓存过期时间,用来指定资源到期的时间(HTTP/1.0)
  • Cache-Control:cache-control: max-age=2592000第一次拿到资源后的2592000秒内(30天),再次发送请求,读取缓存中的信息(HTTP/1.1)
  • 两者同时存在的话,Cache-Control优先级高于Expires

image.png

Cache-Control的一些属性:

  • no-cache :可以在本地缓存,可以在代理服务器缓存,需要先验证才可使用缓存
  • no-store : 禁止浏览器缓存,只能通过服务器获取
  • max-age : 设置资源的过期时间(效果与expires一样)

一般而言,在项目中是两种都设置

image.png

无论资源信息是从强缓存中获取,还是从服务器重新获取,HTTP状态码都是以200为主。

强缓存不适合于静态页面的缓存,如果页面都缓存了,以后服务器更新了产品,我们访问也是走的缓存数据,这样看不到最新的内容了。

2.强缓存模式下,其他资源信息的缓存和部署以及更新

问题:本地缓存了文件,但是服务器对应的资源文件更新了,我们如何保证获取的是最新的内容

  • 请求资源文件「例如CSS/JS/图片...」的时候设置时间戳
  • 基于webpack生成不同的资源名称(eg: xxx.min.css。若代码更新重新打包生成的资源名称就会改变) 「HASH戳」

第一次请求:

<link href='index.css?20220305143712'>
<script src='asfe4356.js'> 
复制代码

过了一天 css/js内容改变了,再次发请求,保证时间戳不一样,这样就不会走本地强缓存了,而是重新从服务器拉取最新的资源。

<link href='index.css?20220306143712'>
<script src='rtlg57903gdbsv.js'>
复制代码

发现后缀和上次缓存的不一样,则重新拉取最新的信息。

HTML文件永远不会去做强缓存!否则无法保证服务器资源更新,客户端可以随时获取最新的信息。

平时项目中,除了静态页面以外,对于CSS/JS/图片一般都会做强缓存。

协商缓存

1.协商缓存的概念

协商缓存(也叫对比缓存)就是强缓存失效(或者不存在)(eg:html可以做协商缓存)后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程

  • 静态页面可以使用协商缓存处理
  • 对于其余的资源文件,我们使用 强缓存+协商缓存

2.协商缓存的两组字段

  • Last-Modified & If-Modified-Since:前者是用来记录服务器资源文件最后一次更新的时间,存在于响应头中;后者是下一次请求相同资源时,浏览器从自己的缓存中找出“不确定是否过期的”缓存,因此在请求头中将上次的 Last-Modified 的值写入到请求头的 If-Modified-Since 字段,服务器会将 If-Modified-Since 的值与 Last-Modified 字段进行对比,若相等,则表示未修改,响应 304,反之响应 200 状态码,并返回数据。

  • Etag & If-None-Match:前者是只要服务器资源文件改变就会生成一个不同的标识(一般都是 hash 生成的)。后者和上一组字段的 If-Modified-Since 的情况相似。服务端同样会将二者进行比较,命中返回 304, 不命中返回新资源和 200。

image.png

两者可以一起使用,ETag优先级更高。

image.png

刷新页面的问题:

  • F5刷新:不使用强缓存,使用协商缓存
  • ctrl+F5:二者都不使用

HTTP1.1中Etag的出现,主要是为了解决几个last-modified比较难解决的问题:

  • 一些文件也许会周期性的更改,但其内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新get。
  • 某些文件修改非常频繁,比如在秒以下的时间内进行修改(如1s内修改了N次),if-modified-since 能检查到的粒度是秒级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)。
  • 某些服务器不能精确的得到文件的最后修改时间。

共同点总结:

强缓存和协商缓存都是服务端设置,客户端浏览器自动执行,无须前端编写代码,而且都是对静态资源文件的处理。

数据缓存

数据缓存就是把不需要经常更新的数据接口,做缓存处理。数据缓存就不能依托服务器和浏览器来自动处理了,它需要JS存储方案以及相关的操作去编码实现。

1.数据缓存的需求

本地没有缓存数据/缓存失效,我们从服务器拉取最新数据(类似于强缓存);本地有缓存数据且未过期,我们直接读取缓存的数据(减少和服务器之间的交互频率,降低服务器压力,也可以提高页面的渲染速度...)

  • 页面不刷新,我们某些内容频繁操作,但是数据不是需要实时获取最新的,则可以做一下缓存;只要页面刷新,重新从服务器获取数据;
  • 页面只要不关闭,我们读取缓存数据「针对于不经常更新的数据」;
  • 页面即使关闭,重新打开,我们也可以读取缓存中的数据「数据更新的频率更低,我们自己设定过期周期」;
  • ...

2.客户端存储数据的方案

  • (全局)变量存储「vuex/redux」:页面刷新或者关闭后重新打开,之前存储的数据都没有了(内存释放问题导致的)

  • 本地存储:即基于JS管理的本地存储方案(都是以明文形式存储,所以重要的信息尽可能不要直接存储,可以采取md5加密)

    • cookie

    • webStorage:localStorage & sessionStorage

    • IndexedDB

    • Web SQL

    • Cache

    • Manifest 离线存储

    • ...

存储到本地的信息都是以字符串形式存储的!

基于JS管理的本地存储方案,都是存储到计算机物理内存中的,全局变量存储是存储在虚拟内存中的。

image.png

3.cookie

操作:通过 document.cookie 实现获取和设置

image.png

特点:

  • 具备有效期:cookie是需要设置过期时间的,超过时间就失效了(在有效期内,不论页面是刷新还是关闭后再重新打开,存储的信息都在),并且有路径等限制!
  • 受“源”和“浏览器”限制:cookie信息只允许同源访问,而且更换浏览器后也无法获取
  • 存储大小有限制:同源下最多只允许存储4KB内容。
  • 具有不稳定性:基于安全卫士或者浏览器自带的清除操作,会把cookie干掉;开启无痕浏览或者隐私模式,则不能存储cookie信息!
  • 兼容性:cookie兼容低版本浏览器。
  • 和服务器之间有“猫腻”:cookie并不算严格的本地存储,和服务器之间有很多的“猫腻”。客户端向服务器发送请求的时候,会默认把本地的cookie信息,基于请求头发送给服务器;并且如果服务器返回的响应头中有Set-Cookie字段,浏览器也会默认把这些信息在客户端本地种上cookie...!(因此本地cookie存储的越多,每一次向服务器发送请求传送的东西也越多,速度也越慢)

4.localStorage

操作:

localStorage.setItem(key,value)
localStorage.getItem(key)
localStorage.removeItem(key)
localStorage.clear() // 清除所有...
复制代码

特点:

  • 持久化本地存储:没有过期时间,页面关闭存储的内容也是在的,只有手动清除(或者卸载浏览器)才会清除
  • 受“源”和“浏览器”限制
  • 存储大小有限制:同源下最多只允许存储5M内容。
  • 具有稳定性:清楚电脑垃圾或历史记录对其存储的信息没有影响,而且无痕模式下也可以存储信息!
  • 和服务器之间毫无关联:除非自己手动将本地的存储信息传递给服务端,否则和服务器没有关系。

实现具备有效期的localStorage

  1. 存储信息的时候,多存储一个关于时间的键值对。
  2. 后期获取的时候,利用当前时间 - 存储时间来验证差值是否在你规定的有效期内。

后面会封装这块的代码实现。

5.sessionStorage

和 localStorage 只有一个区别:localStorage 是持久化存储,页面刷新或关闭,存储的信息还在;sessionStorage 是会话存储,页面刷新存储的信息还在,但页面一旦关闭(会话结束),存储的信息都会被释放。

6.封装 localStorage 实现数据缓存的方法

/**
 * 基于 localStorage 实现数据缓存
 * @param func 该方法执行,可以向服务器发送请求,返回promise实例,并且根据请求结果决定实例的状态
 * @param name localStorage存储信息时的key
 * @param limit 有效期(单位毫秒)默认一小时
 */
 function cacheStorage(func, name, limit) {
  if (typeof func !== 'function') throw new TypeError('func is not function');
  if (typeof name !== 'string') throw new TypeError('name is not string');
  if (typeof limit !== 'number' || isNaN(limit)) limit = 3600000;
  return new Promise(async (resolve, reject) => {
    let result = localStorage.getItem(name),
        now = +new Date();
    if (result) {
    let { time, data } = JSON.parse(result);
        if (now - time < limit) {
            // 缓存有效
            resolve(data);
            return;
        }
    }
    // 缓存失效
    try {
        result = await func();
        localStorage.setItem(name, JSON.stringify({
            time: +new Date(),
            data: result
        }));
        resolve(result);
    } catch (err) {
        reject(err);
    }
  });
}
复制代码
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改