前言
浏览器请求中的缓存策略主要有强缓存和协商缓存两种策略。强缓存是根据服务器响应头字段(Expires或Cache-Control)的过期时间来判断是否需要发送HTTP请求或者读取缓存数据的策略。如果缓存未过期,则直接从缓存中读取数据。协商缓存是当强缓存失效之后,浏览器在请求头中携带相应的缓存tag(Last-Modified 或 ETag)来向服务器发请求,由服务器根据这个tag来决定是否使用缓存。如果服务器判断缓存未改变,则返回304 Not Modified,浏览器从缓存中读取数据。如果服务器判断缓存已改变,则返回新的响应数据。此外,还有一个from memory cache的缓存方式,它是指浏览器直接从内存中读取缓存,通常是已经加载过的资源。但当浏览器关闭后,数据将不存在,再次打开相同的页面时会从磁盘缓存中读取。
Storage:前端数据存储,也是很广泛得存储,都遵循同源策略. 分为sessionStorage和LocalStorage
sessionStorage:
会话存储,容量一般是5M,sessionStorage只作用于当前窗口(同源页面),不能跨窗口读取其他sessionStorage得信息,每次新建,或者关闭一个窗口,都会新建,删除一个SessionStorage.
LocalStorage:作用于浏览器,即时你有多个窗口,他们之间也是共用这个localStorage的,存储内容一般20M左右,不同浏览器或许有偏差,因为这个共享的原因,这就导致XSS(跨站脚本攻击)可能会获取到你的local里的数据,所以很一般不要将重要敏感信息放在这里面
他们本质是都是在读写文件,所以如果你存的太多,也是会影响一点性能的… 使用就是setItem(k,v),getItem(k),removeItem(k),clear()… (可以根据我前面说的浏览器进程之间的关系,联想到session和local是放在浏览器哪个进程里面的)
indexDB:说实话,我也没用过…不过正好去了解了一下 简单说说吧 是H5新增的一个内置数据库.跟NoSQL很像,indexDB存储的是永久保存的数据,适合存储大量结构化数据,这样有些存储在服务器的数据就可以存储在这个数据库了,减轻服务器的负担(其实我觉得根本没必要,用户可不想牺牲自己的内存帮你存大量东西…) IndexDB里的操作是事务性的.一种对象存储在一个objectStore里,object Store就相当于一个关系数据库的表,object Store可以有很多个,object Store里面也可以有很多对象 兼容性:ie11以上才支持 基本使用:
let my = {
name:'JinBeiHuan',
age:22,
sex:'man',
db:null
}
//打开仓库,没有就创建一个
let request = window.indexDB.open(my.name,2)
//indexDB第二个参数是版本号,默认是1,不支持小数
console.log(request)
//打印的是一个IDBOpenDBRequest的对象,里面有一些状态的回调参数,初始都是null,需要手动挂载
request.onerror = function(event){//失败回调}
request.onesucess = function(event){//成功回调
//将返回的值赋给自己控制的db版本对象下面两种方法都能接收到
my.db = event.target.result||request.result
}
//关闭这个对象 之后无法插入,删除等操作
my.db.close();
//删除库 直接删除该db仓库
window.indexDB.deleteDatabase(my.db)
//...别的我就不写了
indexDB还有很多api,我看了一下,还算复杂,它存储空间无限大(只要用户的硬盘够,用户:rnm!) 缺点就是用户可能不太希望牺牲自己的硬盘容量来保存,还有浏览器同源策略对这个访问也会有影响,甚至你用个软件清理垃圾都可能给你清理了…所以我真心觉得,emmm…挺鸡肋
cookie:它的诞生最初就是起到身份标识的作用,因为http是无状态的,所以你每次跟它断开连接之后,下次访问服务器时,他就不认识你了,所以cookie就是一个身份证的作用, 客户端在访问服务端之后,服务端会在他那里存储一些会话信息(session)保存一个状态,然后在响应头set-cookie中传入一个k-y值,然后这个k-y值作为cookie保存在客户端,下次客户端访问时就可以请求头中携带这个cookie作为身份标识,服务端就认识你了.在这里我只简单介绍作用,具体以后讲cookie,token再具体讲讲
言归正传,浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的
按照缓存分类 优先级从上到下 找到就直接返回
Service Worke Memory Worke Disk Cache Push Cache 网络请求 Service Worke 这是一种比较新的技术吧,可以把他理解为客户端和服务器之间的一个代理服务器(中间商) 和web Worker很像,不能操作dom,可以用来做一些js额外的非常耗时的操作,最大的作用就是缓存静态页面达到一个可以离线访问静态页面的一些功能,这是一个大模块,将来再将吧(其实我自己也没用过,等我学完了发篇博客整理一下吧) Memory cache 顾名思义,内存中的缓存,js和图片等文件解析执行后直接存入内存缓存中,刷新页面时只需要直接从内存缓存中读取,以下是他的特点
空间有限 短期存储 读取速度快 disk cache 顾名思义,磁盘中的缓存,disk cache会严格根据http头信息中的各类字段来判定哪些资源是可以缓存的,哪些资源不可以缓存,哪些资源是仍然可用的,哪些资源是过时需要重新请求的 缓存命中后会从磁盘读取资源,虽然比起从内存中读取慢了一些,但是比起网络请求还是快了不少,绝大部分缓存来自disk cache.以下是他的特点 持久存储, 读取速度较慢 Push cache http2.0新增的缓存方式,优先级很低,只有当以上三种缓存都没有名字才会使用,缓存时间短暂,一次会话期(页面打开的那段时间),一旦页面关闭就被释放,并且里面的缓存只能使用一次
都没有找到缓存,那就只能发送网络请求了,此时为了提升之后请求的缓存命中率,要把这个资源加入到缓存中
根据Service Worker中的handler决定是否存入cache Store 根据http头部字段(Cache-Control,Pragma等)决定是否存入disk cache memory cache保存一份资源的引用,以备下次使用 按失效策略分类 分为强制缓存和协商缓存,注意这都是属于disk cacke的范畴
强制缓存 (强缓存) 强制缓存直接减少请求数,是提升最大的缓存策略.如果考虑使用缓存来优化页面性能的话,强制缓存应该是首先考虑的(当然还是根据具体需求来).在请求,处理,响应三阶段都有提升 简单介绍下设置参数:Expires和Cache-Control
expires 这是http1.0的字段,表示缓存到期时间,是一个绝对时间(当前的时间+缓存的时间),你可以看成到期时间,主要缺点
由于是绝对时间,用户的客户端的本地时间如果被修改,就会导致缓存可能失效,就算不修改,时差或者其他因素也可能导致时间错误 写法太复杂了.可读性也差,表示时间的字符串多个空格,少个字母,都会导致非法属性而设置失效 Cache-control 在已知expires的缺点之后,http1.1加入了cache-control字段,这个字段表示资源缓存的最大有效时间,是一个相对时间,在该时间端内,命中缓存不会发起网络请求
cache-control:max-age = 259200
max-age:最大有效时间
must-revalidata:表示如果超过了max-age时间,浏览器必须发起一个网络请求去服务器,验证资源是否还有效
no-cache:虽然字面意思是不要缓存,但是缓存了一些的东西的,只是是否使用这个内容需要后期对比
no-store:真正意义上的不要缓存,所有内容不走缓存,包括强制和协商
public:所有内容都可以被缓存 包括客户端和代理服务器cdn等
private:所有的内容只有客户端可以缓存,代理服务器不能缓存 这是一个默认值
需要注意的是no-cache这个字段有点误导性,设置了no-cache之后,并不是说浏览器不再缓存数据,而是浏览器在使用缓存数据时,需要先确认以下数据是否还跟服务器保持一致,也就是协商缓存.而no-store才是真正的不会缓存,即不使用强缓存也不使用协商缓存
这些值可以混用
cache-control:public,max-age=259200
混用优先级:no-store>no-cache>public/private>max-age
协商缓存 强制缓存失效(超过规定时间时)就需要协商缓存来救场,由服务器决定当前缓存是否失效 流程如下
浏览器先请求缓存数据库,返回一个缓存标识 之后浏览器拿这个标识和服务器通信 如果缓存未失效,就返回http状态码304表示继续使用,于是客户端继续使用缓存 如果失效了,那么就返回新的数据和缓存规则,浏览器响应数据后,再用新规则写入缓存 首先对比缓存是要请求服务器的,但是服务器仅仅返回一个响应行 其中状态码304 并没有实际的内容 所以节省了大部分数据传输时间 其实就是通过压缩减少响应体积,来缩短网络请求时间的,和强制缓存相比没那么快,但总比没有好 对比缓存和强制缓存是可以一起使用的,作为再强制缓存失效后的一种后备方案.实际项目中他们也的确经常一起出现 介绍下协商缓存的使用 Last-Modified & if-Modified-Since
服务器通过Last-Modified字段告知客户端,资源最后一次被修改的时间,例如Last-Modified:FRI,10 Jan 2022 01:40:11 GMT 浏览器将这个值和内容写入缓存中 下一次请求相同资源时,浏览器先从自己的缓存中找出"不确定是否过期的"(就是你现在的时间超过了那个资源最后一次被修改的时间)缓存,因此在请求头中将上次的Last-Modified的值写入请求头的If-Modified-Since字段. 服务器会将这个If-Modified-Since的值和服务器内部的Last-Modified进行对比,如果相等,表示上次修改过后,还没有修改文件,响应一个304,反之,表示修改走正常流程返回数据 缺陷 如果资源更新的速度是秒以下单位,那么该缓存是不能使用的,因为它的时间单位最低是秒 如果文件是通过服务器动态生成的,那么该方法更新的时间永远是生成时间,尽管文件可能没有修改,但也起不到缓存作用
Etag & if-None-Match
Etag存储的是文件的特殊标识(一般都是hash生成的),服务器存储着文件的Etag字段 之后的流程和Last-Modified一样,只是Last-Modified变成了Etag字段和它所标识的文件的hash,把If-Modified-Since,变成了if-None-Match.服务器同样进行比较,找到了对应的文件就返回304否则走正常流程 二者对比
精确度上Etag要优于Last-Modified 优先级上:服务器校验优先考虑Etag 性能上:Etag逊色于Last-Modified 如何去选择缓存策略呢? 1.对于很少变化的资源
cache-control:max-age=31536000 //一年
通常处理这类资源时 给他们的cache-control配置一个很大的max-age,这样浏览器之后请求相同的url直接命中强制缓存
而为了解决更新问题,就需要在文件名(或者路径中)添加hash,版本号等动态资源,之后更改动态字符,以达到更改引用URL的目的,让之前的强制缓存失效,(其实也不是失效,只是不在使用了,因为文件名变了,请求这个资源就不再认为是原来的资源了)
在线类库,jq,lodash等就用的这种模式 2.对于经常变化的资源来说
cache-control:no-cache
这里的资源不一定是静态资源,也可能是网页资源,例如博客文章,这类资源特点是URL不能变化 但是内容却可以变化
我们可以设置上面那个请求头字段来迫使浏览器每次请求都要验证文件是否被修改来判断缓存是否有效
提到了验证,那么Etag或者Last-Modified又怎么去加呢,难道我每次自己去看看是否过期手动添加吗?这里其实有很多类库能帮你加上,并不用去太多关心(要么干脆自己造个轮子来跑) 这个模式节省的不是请求数量,而是请求体
1和2结合使用(反例啊,最好别用) 从上面我所说的情况来说,no-cache就相当于设置了max-age=0,must-revalidata, 但是!浏览器有缓存自动清理机制,你是不能去控制的 假如缓存中有3中资源:index.html index.css index.js 如果你的index.js被清理了,而另外两个存于缓存中,好,另外两个直接用缓存没问题,浏览器去请求新的index.js, 那么,问题来了,这个index,js改了没有呢? 如果改了, 那完了 旧的html文件配上新的js文件,好家伙你拿前朝的剑来斩本朝的官啊 所以就会出乱子 好了 差不多说完了
用户行为对浏览器缓存的影响 打开网页,地址栏输入地址:查找disk-cache,是否命中缓存,没有就发送请求 普通刷新,页面没有被关闭,memory-cache可用,去检查这个 然后检查disk-cache 强制刷新 浏览器不使用缓存 因此发送的请求均带有cache-control:no-cache 最后,如果请求头中没有一个字段确定缓存时间是否过期 那么浏览器会有一个启发式的算法 默认生成一个缓存时间