广义上,前端页面在两次不同的运行时中,能够共享的数据即可成为前端缓存。前端缓存有很多优点,它可以帮助我们记录登录状态,可以帮助我们做跨页面通信,可以帮助我们做页面的的性能优化。但同时,不合理的使用前端强缓存可能会带来很多问题,比如版本未及时更新,顽固缓存导致的bug修复无法及时在CDN上反馈到终端用户。在如今前端缓存策略众多的背景下,我们该如何选择最合适的前端缓存呢?本文将从前端缓存策略、机制、应用场景以及一些特性对比做简要介绍,希望通过本文,笔者和大家都能够对前端缓存能够有一个更加深刻的认识。
关于何为前端缓存,这里结合具体实际给出一个简单的定义:在两次不同的运行时中,能够共享的数据可以成为前端缓存。如何理解两次不同的运行时呢,可以理解为两次不同的页面加载过程。比如加载A页面,得到上下文环境:RunTime1,加载B页面,得到上下文:RunTime2。这两个运行时可以共享数据Front_CacheData_AB。A页面和B页面可以同时在运行时,也可以A页关闭后再打开B页面。下图1比较形象的给出了前端缓存的这种定义~
前端缓存按照是否需要后端参与,可以分为:浏览器缓存和HTTP缓存。前端按照缓存的数据类型,可以分为文件(应用)级的缓存:Application Cache和存储型的缓存:Storage。
其中Application缓存又分为manifest和service worker。两者都旨在文件级上提升页面加载性能减少网络请求。
其中Storage里面又包含常见的cookie,localStorage,sessionStorage,IndexedDB,webSQL。这类缓存是轻量级的缓存,主要是对一些前端的业务数据做本地持久化存储,一般保存一些状态、或者一些小文件,或者做登录校验等。
其中HTTP缓存不同于前两者,这种缓存是基于HTTP头的一些信息由浏览器和服务器共同决定缓存策略。相较于前两者,更加底层,js基本无法控制。HTTP缓存又分为强缓存和协商缓存,下文将做详细比较和介绍。HTTP缓存主要在减少网络请求、降低服务器压力,提升前端页面加载性能等场景下使用。
图2将形象的给出当下前端缓存的简单分类图~
本文作为前端缓存的上篇,将主要讲解浏览器缓存。HTTP缓存将作为下篇在后续和大家分享。
在一个具体的工程项目中,我们该如何使用缓存来帮助我们解决问题,或者优化我们的工程是一个值得讨论的事情。笔者将在下面的内容中对每一种前端缓存进行简单的分析,并且给出建议使用的场景。
应用缓存,顾名思义就是在应用层级中使用的缓存,该缓存一般以远程资源文件为缓存单位,比如一个css样式文件,或者一个公共的jssdk等。
在说H5离线缓存之前,不得不说一点:H5离线缓存目前已经处于被废弃的状态(不推荐使用)。
H5离线缓存是一种基于缓存清单标记的缓存机制,缓存清单(manifest)给出了三类资源:需要缓存的资源(CACHE)、需要在线获取的资源(NETWORK)、获取失败的兜底资源(FALLBACK)。
>1 开启方式
开启H5离线缓存需要在html指定相关的标记:manifest,如下:
<html manifest="mycache.manifest">
<!-- Content... -->
</html>
其中,html标签中指定manifest属性的值即为缓存清单文件,浏览器第一次打开app的时候会读取这个文件,然后会读取里面的配置做相应的缓存处理。
>2 manifest文件格式
CACHE MANIFEST
# v1 date:2020-1-20
# this is the first version
index.html
index.js
script/
css/common.css
# Use from network if available
NETWORK:
login.html
# Fallback content
FALLBACK:
login.html login2.html
注意:CACHE MANIFEST也可以写成 CACHE:
此外,manifest文件必须以text/cache-manifest 的MIME类型传输,不然可能无法被浏览器解析。
>3 manifest 配置说明
CACHE: 或 CACHE MANIFEST
该标记之后的每一行都表示一个显示的缓存资源,这些资源会在它们第一次被下载之后缓存起来,以供下一次使用。可使用相对/绝对地址。
NETWORK:
该标记之后的每一行都表示一个需要显示使用网络请求的资源,这些资源将会忽略所有缓存,配置的时候可以使用通配符。可使用相对/绝对地址。
FALLBACK:
该标记之后的每一行都有2个uri,第一个uri表示请求的资源,第二个uri表示后备资源,当第一个uri的资源访问不可达的时候,将会使用第二个uri代替,可以使用通配符。FALLBACK必须使用相对地址。
注意:所有的标记后面的资源uri都必须和manifest同源,manifest文件的地址必须与应用url同源。
>4 更新缓存
浏览器会在两种情况下更新缓存:
1)manifest清单文件在服务器上发生了变更
2)manifest清单文件中列出的资源在服务器发生了变更
>5 使用场景
这种缓存的典型使用场景是离线应用,即断网的情况下,如果不是第一次打开应用(页面),也能够通过manifest加载所有已经缓存的静态资源。其次缓存的使用也加快了页面加载的速度,而且也减轻了服务器的压力。
作为H5离线缓存的替代方案,Service Workers横空出世。除了和H5离线缓存一样具备离线缓存的功能外,Service Workers主要提供了更加复杂的缓存控制和管理的接口。比如开发这可以使用自定义的body和header组成一个http回包作为请求的返回提供给浏览器。具备更加精确的控制能力。
在说service worker之前,先通过图3看一下service主要的工作流程:
>1 注册
注册worker是指将service worker的处理逻辑以脚本文件的形式给到浏览器~下面是一个标准的注册逻辑,ams/sw.js是service worker的主逻辑脚本文件。scope:“/ams/”表示该service worker将只能处理该目录下的请求。作用域不能大于sw.js的根目录。
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/ams/sw.js', { scope: '/ams/' }).then(function(reg) {
// 注册成功
console.log('注册成功. 作用域是' + reg.scope);
}).catch(function(error) {
// 注册失败
console.log('注册失败: ' + error);
});
}
>2 安装
安装发生在注册成功之后,主要是对缓存进行定义。例如使用如下代码进行安装和缓存定义:
this.addEventListener('install', function(event) {
event.waitUntil(
caches.open('v1').then(function(cache) {
return cache.addAll([
'/ams/',
'/ams/index.html',
'/ams/css/comm.css'
]);
})
);
});
>3 激活
安装完成后即进入激活阶段,如果是第一次激活,那么激活逻辑将会在安装之后直接自动执行,如果worker有更新,那么激活逻辑将会在新worker安装之后,并且当前没有页面正在使用老的worker时候进行激活。
>4 处理请求
fetch事件是一个很有用的事件,这个事件将会告诉worker当前有网络请求,并且需要决定该请求是走缓存还是自定义处理。下面的示例将告诉我们优先走缓存,然后在走网络:
this.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(function(response) {
return response || fetch(event.request);
})
);
});
>5 使用场景
综合service worker的工作原理和提供的能力,service worker更适合以下工作场景:
1)需要精准控制资源缓存的
2)需要精准定制资源的回包内容和针对请求做特殊处理的需求
注:service worker的兼容性没有H5离线缓存好,详情请见:兼容性对比
这里的storage主要是指轻量级的数据级别的缓存,比如一个json对象,一个标志位,一个用户登录态等。下面将针对4个不同类型的Storage进行简单阐述。
1)什么是Cookie
了解cookie的工作原理之前,我们先来了解什么是cookie,用笔者自己的理解方式就是:Cookie是一个key-value的键值对,此外还定了过期时间(Expires)、作用域名(Domain)、作用路径(Path)等属性。Cooke可以由客户端指定,也可以由服务端设置,并返回给前端,此外任意一个HTTP链接都会发送同域或者父域的cookie到后端程序。下面的图4给出了Cookie的主要工作流程:
2)Cookie的组成
属性 | 描述 |
---|---|
Name | Cookie的名字 |
Value | Cookie的值 |
max-age | 定义Cookie的过期时间,以秒为单位,表示cookie将在max-age秒后过期 |
Expires | Cookie的过期时间,GMT或者UTC时间 |
Domain | Cookie的作用域名 |
Path | Cookie在Domain上的作用路径,和domain一起构成了作用范围 |
Secure | 标志Cookie只通过HTTPS传递 |
HttpOnly | (仅后端设置)标志Cookie不能被前端页面读写,只能通过http/https协议传递 |
3)最大允许空间
关于数量
除了webkit内核的浏览器没有数量限制之外,其余浏览器内核都对单个域名下的cookie数量做了限制:50个,具体数量可能会随着浏览器版本更新或者具体的浏览器发行版本的控制有所不同。
关于大小
绝大多数浏览器单个域名下前端允许的最大cookie大小为4096Bytes(即4KB左右),超过将无法添加新的cookie。
4)使用场景
由于Cookie特有的前后端支持的特性,所以Cookie特别适合做登录态保持,身份鉴权,一些用户偏好的离线保存等。
在说localStorage和sessionStorage之前,先简单说一下Web Storage定义:旨在提供一种
比使用Cookie更直观的方式存储键/值对。它有两种存在形式:localStorage和sessionStorage。
Storage API提供了如下基本的api供开发者使用:
| API | 描述 |
| - | - ||
setItem(key,value) | 设置一个键值对
getItem(key) | 获取Storage中的一个键的值
removeItem(key) | 移除Storage中对应的key和value
clear | 清空对应domain中的所有Storage的键值对
1)sessionStorage和localStorage的不同
sessionStorage和localStorage的api和调用方式全部相同,只是sessionStorage只在浏览器会话期间有效,而localStorage则会一直保存,下一次浏览器打开仍然生效,直到你主动删除它。
2)最大允许空间
这里给出大部分浏览器对SessionStorage/localStorage最大空间的支持:2MB-10MB。实际以具体浏览器的表现为主。
3)注意事项
由于sessionStorage/localStorage的操作是同步的/阻塞的,所以在高频操作sessionStorage/localStorage的时候,一方面会产生性能问题,另一方面会产生异常。所以建议进行异常捕获和处理:
//设置指定key-value的值
funciton setValue(k,v){
try{
if('sessionStorage' in window){
window.sessionStorage.setItem(k,v);
}else{
alert('不兼容sessionStorage');
}
}catch(e){
alert('处理错误');
}
}
4)兼容性
实时兼容性详见:点击查看实时兼容性
5)使用场景
同样是key-value,Storage比cookie的优势在于:空间远大于cookie,可以存储长文本,可以存储一些较大的数据,比如:全国行政区划的详细信息,这类信息在某些应用(比如OTA类)中使用高频,且数据量超过了cookie的存储。如果需要长期存储(下一次打开app仍然需要)可以使用localStorage,如果存储的数据只是在会话期间使用,比如用于tab页面间共享数据等,可以使用sessionStorage。
IndexedDB更像一个NoSQL数据库,数据 以键值对的形式保存。每一条数据存储都有一个主键,主键是独一无二的。
1)基本概念
概念 | 描述 |
---|---|
数据库 | IDBDatabase 对象,类似传统数据库的DB |
对象仓库 | IDBObjectStore 类似于传统数据库的Table,每一个IDBDatabase都包含若干个DIBObjectStore |
索引 | IDBIndex对象。和传统数据库中的索引功能类似,加快检索数据,调用IDBObjectStore的createIndex方法可以创建索引 |
事务 | IDBTransaction对象。和传统数据库操作中的事务概念类似,表示一系列操作的整体性,如果某一步出错,则整体操作回滚 |
操作请求 | window.indexedDB.open方法将创建一个操作请求:IDBRequest对象。 |
2)基本操作
操作 | 调用方法 |
---|---|
打开数据库/新建数据库 | window.indexedDB.open |
读取数据 | dbObjectStore.get |
更新数据 | dbObjectStore.put |
新增数据 | dbObjectStore.add |
删除数据 | dbObjectStore.delete |
详细使用方法可以参考:https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API
3)最大允许空间
和cookie,localStorage不同,indexedDB致力于轻量级浏览器数据库,其存储空间至少可以达到250MB以上,不同浏览器可能会不同,以实际浏览器制定的规范为主。
4)一些特性
1 异步
和localStorage最大的不同莫过于,indexedDB是异步的,开发者在使用的时候并不会阻塞当前页面,页面的性能得到了保证。
2 数据库相关特性
虽然indexedDB不是传统意义上的数据库,但是其设计初衷是希望能够在浏览器上提供一个类似于NoSQL数据库形式的接口给到开发者使用,所以一些数据库中的特性也被引入过来,比如:事务操作,索引,主键等,这些都是很好的一些特性
3 支持二进制存储
和Cookie,localStorage等不同的是,indexedDB几乎支持任何类型的数据存储,包括文本数据,二进制数据,js对象等,对于需要直接存储二进制数据的需求特别友好。
5)兼容性:
实时兼容性详见:点击查看兼容性
6)使用场景
基于indexedDB的特性,我们推荐indexedDB做前端的大数据量的存储,前端缓存博客草稿,前端缓存本人的操作日志,一些表单中上传图片资源,音频资源的缓存,以便下一次访问app能够直接获取最近使用的资源等。
此外,一些需要支持纯离线状态下获取数据,增删改查的操作场景,比如:便签、笔记类、可以一条一条存储在indexedDB中,如果支持联网,则可以一次性同步到云端。
在说WebSQL之前,需要强调一点,和H5离线缓存(manifest)一样,WebSQL已经不推荐使用了,推荐使用IndexedDB来作为前端数据库存储,W3C已经做废弃处理了。下面简要对WebSQL的概念和相关用法做一些描述:
1)概念
WebSQL同IndexedDB类似,都属于前端数据库存储的一种,和IndexedDB不同的是,WebSQL是关系型数据库,所有的关于库,表的操作都通过SQL语句进行。
2)关键API
和indexedDB不同的是,WebSQL大部分调用都通过传SQL语句进行的,所以WebSQL的关键API非常少,只有3个:
API | 说明 |
---|---|
openDatabase | window的全局方法,用于打开/新建一个数据库连接,调用成功返回一个db实例 |
transaction | 挂在db实例下的方法,用于事务操作 |
executeSql | 挂在事务实例下的方法,用于执行具体的SQL语句 |
3)简单的Demo
//先判断是否支持WebSQL
if(!window.openDatabase){
throw 'not support WebSQL';
}else{
let dbConfig={
name:'testDB',
version:'1',
desc:'mall',
size:1024*1024*10//单位Byte
}
//链接db
let db=window.openDatabase(dbConfig.name,dbConfig.version,dbConfig.desc,dbConfig.size);
//执行事务封装
db.transaction(tx=>{
//tx是事务实例
//查询数据
tx.executeSql('select * from orderlist',[],(tx,result)=>{
//result是SQL查询结果
console.log('查询成功',result);
},(tx,error)=>{
console.log('查询失败',error);
});
})
}
4)浏览器兼容性
可能由于WebSQL被废弃的原因,其兼容性不是很理想:
实时兼容性详见:点击查看兼容性
5)存储空间大小
不同浏览器对存储空间的大小限制不一致,具体在使用过程中要积极处理异常即可。
6)使用环境
由于WebSQL也属于前端数据库型的缓存机制,而且属于关系型数据库。建议有关系型数据的存储需求的应用使用,而且WebSQL不能存储二进制的数据,需要注意。
本文对前端缓存中的浏览器缓存的类型和特性原理做了简要的介绍,并且都给予了使用环境的推荐。下面我们将从主要的存储空间、兼容性、性能、是否过期控制、控制粒度、作用域、功能性等维度对这几种缓存类型做一个简单的对比:
缓存 | 存储空间 | 兼容性 | 操作性能 | 是否过期控制 | 控制粒度 | 作用域 | 功能性 |
---|---|---|---|---|---|---|---|
H5离线缓存 | 理论上无限制,取决于硬件 | IE10+ 总96%+ | 性能良好,运行在后台 | manifest控制 | 文件级、resource | manifest同源 | 控制走缓、network、兜底,功能性一般,W3C废弃,推荐替代方案:Service Worker |
Service Worker | 理论上无限制、取决于硬件 | IE不支持 总92% | 运行在后台,性能良好 | sw.js文件控制 | 文件级,HTTP 回包级 | sw.js根目录 | 控制缓存、添加缓存、控制走网络、自定义缓存内容(资源内容),功能性强 |
Cookie | 4KB左右 | 几乎都兼容 | 同步操作,性能良好 | 属性可控制 | 粒度细,变量,标志位等 | 页面同域,或者同父域 | js控制key-value,过期,删除,添加,更改等 |
localStorage/SessionStorage | 2MB-10MB左右 | IE8+ 总93% | 同步,阻塞,高频操作性能一般 | sessionStorage随会话过期,localStorage不过期,需手动清除 | 粒度细,变量,小数据级 | 页面同源 | 可以设置、删除、获取 |
indexedDB | 250MB以上 空间大 | IE10+部分支持 总95% | 异步操作,性能较高 | 无法操作,手动清除 | 粒度细,数据级、文本级、支持大量数据、支持高效查询 | 页面同源 | 支持key-value,主键、索引、二进制存储,功能性强 |
WebSQL | 空间大,百MB级别 | IE不支持,Firefox,Safari新版废弃,兼容性不好 | 异步操作,性能较高 | 无法操作,手动清除 | 粒度细,记录、表、库 | 页面同源 | 支持关系型数据存储,SQL语句查询,不支持二进制,功能性强,但W3C废弃,推荐替代方案:idnexedDB |
笔者水平有限,希望自己的总结和理解能够给大家提供便利,文中描述如有不妥之处,还欢迎随时和我交流~
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。