http资源缓存
简介
如果每个web网络请求都把响应相应的资源的话,会带来更多的带宽消耗和更差的用户体验(更多的等待时间)。因此在网络请求中,使用了缓存机制。缓存有很多种,包括服务器端缓存、代理服务器缓存、cdn缓存、客户端缓存等等。本文主要讨论客户端缓存。
客户端缓存一般有两种机制。
如果一个资源一般情况下不会改变,那么,我们可以在响应里加入一个消息,告诉浏览器,我在24小时内基本不可能改变,那么浏览器在24小时内再次请求改资源就会直接读取浏览器中的缓存,而不会再向web服务器发出请求。这是第一种机制:强缓存。
但是资源在24小时之后还是可能未变,这时也不必返回新的资源,这就要使用第二种缓存机制:协商缓存。那就是当浏览器向服务器发出请求时,服务器会判断资源是否发生过改变,如果改变了,就返回新的资源,如果没有改变,就返回304,通知浏览器,资源未变,你直接读缓存就行了。你可能觉得,那不是还是发出了一次请求吗?但是如果资源比较大的话,还是可以节省带宽和时间的。
强缓存和协商缓存
强缓存
第一种客户端缓存机制涉及两种主要的响应头,expires和cache-control。expires指明了资源过期截止时间,cache-control则指明了过期时长(max-age=XXX)。两者类似,不过cache-control除了max-age之外,还有更多的选项,控制更加精细。
使用强缓存的时候不会发送HTTP请求,直接从缓存中读取资源,返回状态码为 200,size 显示为 from disk cache 或 from memory cache。协商缓存
包括(etag / if-none-match)和(last-modified / if-modified-since)。
last-modified / if-modified-since
last-modified是响应头,if-modified-since是请求头。 这个缓存方案会由浏览器发出请求时间(if-modified-since),然后服务器会对比资源最终修改时间,若在请求时间之后又修改过资源,则返回200及资源,否则返回304,表示可读缓存。
etag / if-none-match
if-none-match是请求头,etag是响应头。这个缓存方案会由浏览器发出资源的摘要,类似md5(但不等价于md5)(if-none-match),然后服务器会对比资源的摘要,若两者不同说明资源有改动,返回200及新资源,否则返回304。etag更加精细,它相比last-modified方案有以下优点:
- 可以应对资源改动但modified时间未变的情形。因为last-modified精度为秒,若资源在一秒之内有多次改动,则last-modified不会返回新资源。
- 可以应对资源未改动但modified时间改变的情形。有些资源定期刷新,但资源未变,这时last-modified会返回新的资源,实际不必。
但是etag要在每次资源改变时进行计算,因此耗费更多服务器的计算性能。因此酌情使用。
etag / if-none-match优先级高于last-modified / if-modified-since。
总结
客户端请求到响应的整个过程中的缓存运作是 强缓存 ->协商缓存。即先看过期时间,若未过期读缓存,过期则想服务器发出请求,进入第二种缓存机制,服务器判断是否需要返回新资源,是则返回200及更新的新资源,否则返回304,让浏览器读取缓存。
cache-control
简介
cache-control是http协议中用于控制缓存的字段,它可能出现在请求头也可能出现在响应头中(大部分情况我们讨论的是响应头中的cache-control字段)。
在之前http通过Pragma控制缓存。Pragma是旧产物,已经逐步抛弃,有些网站为了向下兼容还保留了这两个字段。
Pragma有两个字段Pragma和Expires。Pragma的值为no-cache时,表示禁用缓存,Expires的值是一个GMT时间,表示该缓存的有效时间。
如果一个报文中同时出现Pragma和Cache-Control时,以Pragma为准。同时出现Cache-Control和Expires时,以Cache-Control为准。即优先级从高到低是 Pragma -> Cache-Control -> Expires。
请求头中的cache-control字段
在请求中使用Cache-Control 时,它可选的值有:字段名 | 说明 |
---|---|
no-cache | 告知(代理)服务器不使用缓存,要求向原服务器发起请求。 |
no-store | 可以在客户端存储资源,每次都必须去服务端做新鲜度校验,来决定从服务端获取新的资源(200)还是使用客户端缓存(304)。也就是所谓的协商缓存。 |
max-age=delta-seconds | 告知服务器客户端希望接收一个存在时间不大于delta-seconds的资源。 |
max-stale[=delta-seconds] | 告知(代理)服务器客户端希望接收一个超过缓存时间(若有定义delta-seconds,则为delta-seconds,否则为任意超出时间)的资源。 |
min-fresh=delta-seconds | 告知(代理)服务器客户端希望接收一个在delta-seconds内被更新过的资源。 |
no-transform | 告知(代理)服务器客户端希望获取实体数据没有被转化(比如压缩)过的资源 |
only-if-cached | 告知(代理)服务器客户端希望获取缓存的内容(若有),而不用向原服务器发送请求。 |
cache-extension | 自定义扩展值,若服务器不识别则被忽略。 |
响应头中的cache-control字段
第一,可缓存性:public private no-cache。可缓存性指http的response进过的哪些地方可以进行缓存。public指在response返回经过的任何地方都可以缓存,包括代理服务器,客户端等,这样下次请求将不会到达服务端而直接返回response;private则表示只有返回的浏览器才可以进行缓存;no-cache则表示不可直接用缓存,而是先要到服务器端进行验证。
第二,到期:max-age=
第三,重新验证:must-revalidate和proxy-revalidate,这两个表示缓存过期时间到达以后,必须要到服务端重新请求和重新验证,这两个属性在浏览器也不怎么出现。
第四,其他,no-store,表示本地和代理服务器都不可以用缓存,必须去重新获取;no-transform,告诉代理服务器不要对返回的body进行处理,比如压缩等(代理服务器比如nginx等可以不遵守,但是这个是规范,最好遵守)。
meta标签中的缓存控制
meta标签可以控制清除缓存,即html页面不被缓存
//meta标签清理缓存
//如果需要在html页面上设置不缓存,这在<head>标签中加入如下语句:
//用于设定禁止浏览器从本地机的缓存中调阅页面内容
<meta http-equiv="Pragma" content="no-cache">
//Cache-Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。
<meta http-equiv="Cache-Control" content="no-cache">
//可以用于设定网页的到期时间
<meta http-equiv="Expires" content="0">
//清除浏览器中的缓存,它和其它几句合起来用,就可以使你再次进入曾经访问过的页面时,ie浏览器必须从服务端下载最新的内容,达到刷新的效果。
浏览器刷新姿势
不同刷新页面方式对应不同的缓存策略。
地址栏回车刷新
允许读取本地缓存,只要没过期就可以使用本地缓存的资源。
F5/ctr+R刷新
走协商缓存,对服务器发起请求,若协商结果是304则读取本地缓存。
ctr+F5刷新
强制刷新,所有请求从服务器返回新资源。
参考文档
https://zhuanlan.zhihu.com/p/361405597
https://blog.csdn.net/muzidigbig/article/details/123176484
https://www.jianshu.com/p/de05250e913b
https://www.jianshu.com/p/261b9dbb0720
https://www.jianshu.com/p/e83be77a47a2
https://blog.csdn.net/u012375924/article/details/82806617
https://zhuanlan.zhihu.com/p/79042406
https://juejin.cn/post/6844903751493369870
https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.1
https://www.jianshu.com/p/4da36738dca0
https://www.php.cn/faq/373719.html
https://www.cnblogs.com/frontendBY/p/4775763.html
浏览器数据缓存
简述
上面讨论的http缓存是资源(js、css、图片、字体等文件)的缓存机制,工作在加载资源场景。
还有一种缓存是数据缓存,用来在页面刷新或者重新打开时候保存一些数据。
cookie
概念
cookie的用途是记录用户状态。
一般由服务器生成,可设置失效时间,如果不设置失效时间,默认和页面生命周期相同,关闭页面就被销毁。如果在浏览器端生成Cookie,默认是关闭浏览器后失效。
服务器生成cookie并在响应头中给到浏览器后,以后向该域名的接口发送请求,会自动携带cookie,从而实现了用户状态在不同接口的共享。
使用
设置语法
服务器设置Cookie的方法就是添加响应头
Set-Cookie: <cookie-name>=<cookie-value>;(可选参数1);(可选参数2)
客户端设置Cookie:
document.cookie = "<cookie-name>=<cookie-value>;(可选参数1);(可选参数2)"
可选参数
Expires=<date>
:cookie的最长有效时间,若不设置则cookie生命期与会话期相同
Max-Age=<non-zero-digit>
:cookie生成后失效的秒数
Domain=<domain-value>
:指定cookie可以送达的主机域名,若一级域名设置了则二级域名也能获取。
Path=<path-value>
:指定一个URL,例如指定path=/docs,则”/docs”、”/docs/Web/“、”/docs/Web/Http”均满足匹配条件
Secure
:必须在请求使用SSL或HTTPS协议的时候cookie才回被发送到服务器
HttpOnly
:客户端无法更改Cookie,客户端设置cookie时不能使用这个参数,一般是服务器端使用
使用示例:
// Set-Cookie: sessionid=aes7a8; HttpOnly; Path=/
document.cookie = "KMKNKK=1234;Sercure"
可选前缀
__Secure-
:以__Secure-为前缀的cookie,必须与secure属性一同设置,同时必须应用于安全页面(即使用HTTPS)
__Host-
:以__Host-为前缀的cookie,必须与secure属性一同设置,同时必须应用于安全页面(即使用HTTPS)。必须不能设置domian属性(这样可以防止二级域名获取一级域名的cookie),path属性的值必须为”/“。
前缀使用示例:
// Set-Cookie: __Secure-ID=123; Secure; Domain=example.com
// Set-Cookie: __Host-ID=123; Secure; Path=/
document.cookie = "__Secure-KMKNKK=1234;Sercure"
document.cookie = "__Host-KMKNKK=1234;Sercure;path=/"
cookie和session
严格说session不是一种API,只是使用cookie实现用户状态跟踪的一种技术。
因为使用cookie保存用户状态时候,如果数据量大,会导致每次请求携带数据过多,可以使用session技术解决这个问题。
session技术的核心思路是,服务器生成用户状态数据数据之后不直接放到响应头发给浏览器,而是
- 把用户状态数据值保存在后端存储中(通常是缓存)
- 然后生成一个sessionId关联这个用户状态数据
- 再把sessionId放到cookie中返回给浏览器
这样每次服务接收到请求之后,会拿到cookie中的sessionId,再从后端存储中获取用户数据并处理。
localStorage和sessionStorage
localStorage
localStorage是用来缓存一些持久化的数据的,比如填写一个表单时候可以把输入内容缓存到本地,这样如果不小心关闭页面,再打开时候可以恢复表单已填内容,增强用户体验。再如一些页面偏好设置也可以用localStorage保存。
localStorage的API很简单
localStorage属性
localStorage.length
只读返回一个整数,表示存储在 Storage 对象中的数据项数量。
localStorage方法
localStorage.key()
该方法接受一个数值 n 作为参数,并返回存储中的第 n 个键名。localStorage.getItem()
该方法接受一个键名作为参数,返回键名对应的值。localStorage.setItem()
该方法接受一个键名和值作为参数,将会把键值对添加到存储中,如果键名存在,则更新其对应的值。localStorage.removeItem()
该方法接受一个键名作为参数,并把该键名从存储中删除。localStorage.clear()
调用该方法会清空存储中的所有键名。
浏览器可能提供更方便的API,如可以直接通过对象赋值方式设置数据localStorage['itemKey'] = 'itemValue'
,可以通过对象键值对方式直接获取数据console.log(localStorage['itemKey'])
。
需要
注意事项
- localStorage 写入的时候,如果超出容量会报错(所以设置localStorage时候最好try catch),但之前保存的数据不会丢失。
- localStorage 存储容量快要满的时候,getItem 方法性能会急剧下降。
- web storage 在保存复杂数据类型时,较为依赖 JSON.stringify,在移动端性能问题比较明显。
sessionStorage
sessionStorage和localStorage一样也是用来在前端持久化数据,区别是sessionStorage不如localStorage更持久。
两者其实都拥有一个相同的原型对象Storage,所以API是一样的。
sessionStorage和localStorage最大的区别是它的生命周期,localStorage数据不会过期,sessionStorage数据在页面会话结束时会被清除。
- 页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。
- 在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为新会话的上下文,这点和 session cookie 的运行方式不同。
- 打开多个相同的 URL 的 Tabs 页面,会创建各自的 sessionStorage。
- 关闭对应浏览器标签或窗口,会清除对应的 sessionStorage。
cookie和localStorage
cookie和localStorage作为两种常见的缓存能力尝放在一起比较。其实它们的使用场景完全不同,实际开发时候不可能存在混淆的问题,所以这种对比能起到的唯一作用是更清楚地理解这两个概念。
cookie是为了做用户状态跟踪,而localStorage是用来保存数据,以便页面关闭后再打开还能恢复一些数据而不用再次请求后端服务。
两者区别主要有
- cookie数据在请求接口时候服务器可读取(而且通常都是服务器在使用),localStorage只有前端可以读取。
- localStorage是JavaScript API,js可以读写,cookie则可以被服务器设置为只读,js就修改不了。
- 大小不同,localStorage可以存储几M的数据(不同浏览器不同,Chrome5M左右),cookie只有4KB
- 有效期不同,localStorage不会过期,cookie如果未设置有效期会在会话结束(页面关闭)后销毁。
indexDB
indexDB是前端用来存储大量数据的解决方案。
在之前有Web SQL关系型数据库,并且大部分浏览器都实现了该功能,但在2010W3C废弃了。现在主流的大数据量存储方案就是indexDB。
indexDB的特点
- 键值对储存。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以”键值对”的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
- 异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
- 支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
- 同源限制。IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
- 储存空间大。 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
- 支持二进制储存。 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
主要概念
数据库
数据库是一系列相关数据的容器。每个域名(严格的说,是协议 + 域名 + 端口)都可以新建任意多个数据库。
IndexedDB 数据库有版本的概念。同一个时刻,只能有一个版本的数据库存在。如果要修改数据库结构(新增或删除表、索引或者主键),只能通过升级数据库版本完成。
对象仓库
每个数据库包含若干个对象仓库(object store)。它类似于关系型数据库的表格。
数据记录
对象仓库保存的是数据记录。每条记录类似于关系型数据库的行,但是只有主键和数据体两部分。主键用来建立默认的索引,必须是不同的,否则会报错。主键可以是数据记录里面的一个属性,也可以指定为一个递增的整数编号。
{ id: 1, text: 'foo' }
上面的对象中,id属性可以当作主键。
数据体可以是任意数据类型,不限于对象,甚至可以是二进制类型。
索引
为了加速数据的检索,可以在对象仓库里面,为不同的属性建立索引。
事务
数据记录的读写和删改,都要通过事务完成。事务对象提供error、abort和complete三个事件,用来监听操作结果。
核心操作
- 创建/打开数据库
- 增删改查记录
- 遍历数据
- 创建/使用索引
参考文档
更多详细内容参考浏览器数据库 IndexedDB 入门教程 - 阮一峰的网络日志
参考文档
http://www.ruanyifeng.com/blog/2018/07/indexeddb.html
https://juejin.cn/post/6844903989096497159?searchId=202401180823177880C9D84FFBA38587F8https://juejin.cn/post/7086764922507378702?searchId=202401180823177880C9D84FFBA38587F8
https://developer.mozilla.org/zh-CN/docs/Web/API/Storage
https://juejin.cn/post/6844903587764502536?searchId=202401180823177880C9D84FFBA38587F8
https://juejin.cn/post/6844903989096497159?searchId=202401180823177880C9D84FFBA38587F8
https://juejin.cn/post/7086764922507378702?searchId=202401180823177880C9D84FFBA38587F8