前言
最近在做一个第三方平台跳转登陆的需求,由该需求可以发散到对应的知识有:浏览器缓存、单点登录等。为此我也想到了一个接下来让自己去适应的学习计划,那就是基于当前工作中项目需求里需要用到的知识去扩展学习,以此类推,下个需求又有哪些知识点,再去学习那方面相关的知识。至于学习的深度、拓展到多少相关知识点,则由该段时间自由度来决定。
缓存
缓存: 一般指的都是静态资源文件的缓存,这个一般是客户端和服务器端根据一些协商的规则,自动去完成的缓存策略(不用我们自己编写啥代码去处理)。只有API接口数据缓存,是需要前端开发自己去完成的
1.为什么我们需要浏览器缓存?
当我们访问同一个页面时,请求资源、数据都是需要一定的耗时,如果可以将一些资源缓存下来,那么从第二次访问开始,就可以减少加载时间,提高用户体验,也能减轻服务器的压力。
2.缓存的分类
Web 缓存大致可以分为:数据库缓存、服务器端缓存(代理服务器缓存、CDN 缓存)、浏览器缓存。而浏览器缓存也包含了很多内容: HTTP 缓存(强缓存和协商缓存)、数据缓存(cookie、localstorage、indexDB...) 等。
3.缓存的位置
若设置了缓存机制,则从服务器获取的信息会存储在:
- 虚拟内存(内存条):Memory Cache(一般用于页面没有关闭,只是刷新)
- 物理内存(硬盘): Disk Cache(页面关闭后重新打开) 换而言之,客户端/浏览器会将缓存放在内存中一份,放在硬盘中一份。内存中的缓存,页面一关闭存储的信息就被释放了;硬盘中的缓存,是持久存储,页面关闭还有,除非自己使用一些手段清除掉,如 360清除浏览器缓存。
具体处理步骤:
- 打开网页:先读 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
Cache-Control的一些属性:
- no-cache :可以在本地缓存,可以在代理服务器缓存,需要先验证才可使用缓存
- no-store : 禁止浏览器缓存,只能通过服务器获取
- max-age : 设置资源的过期时间(效果与expires一样)
一般而言,在项目中是两种都设置
无论资源信息是从强缓存中获取,还是从服务器重新获取,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。
两者可以一起使用,ETag优先级更高。
刷新页面的问题:
- 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管理的本地存储方案,都是存储到计算机物理内存中的,全局变量存储是存储在虚拟内存中的。
3.cookie
操作:通过 document.cookie 实现获取和设置
特点:
- 具备有效期: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
- 存储信息的时候,多存储一个关于时间的键值对。
- 后期获取的时候,利用当前时间 - 存储时间来验证差值是否在你规定的有效期内。
后面会封装这块的代码实现。
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);
}
});
}