在前端开发过程中,为了更方便的与服务端交互或者提升用户体验,我们会在客户端(用户)本地保存一些数据,比如cookie/localStorage/sessionStorage。在后台管理系统的前端,会涉及到一些大数据的请求。有时一个接口可以达到5M甚至15M。当接口数据不经常更新时,我们可以采用两种方式,一种是Paging请求+预加载+懒加载,一种是本地存储+热更新。并且因为第二种方式的用户体验更好,所以也是我经常使用的方式。本文针对客户端本地存储,主要讲四种技术:cookie/localStorage/sessionStorage/indexedDB。cookieHTTPCookie通常简称为cookie。这个标准是浏览器在发起HTTP请求时用来存储session信息和携带Cookie参数的://RequestHeaderGET/oss/index.php?r=api/jlog/collectHTTP/1.1Host:gzhxy.baidu.com:8090Connection:keep-alivePragma:no-cacheCache-Control:no-cacheX-Requested-With:XMLHttpRequestUser-Agent:Mozilla/5.0(Macintosh;IntelMacOSX10_12_6)AppleWebKit/537.36(KHTML,likeGecko)Chrome/63.0.3239.84Safari/537.36···Cookie:name=Leon;age=24Cookie有一些限制:它是一串由;分隔的键值对。(分号+一个空格),网络传输时必须是URL编码。它绑定在一个固定的域名下,不允许其他域名访问。有的浏览器有数量限制,超过限制后删除顺序不统一,有的是最近最少使用(LRU),有的随机删除同一域名总大小限制为5KB,之后默默失败超过限制。区分大小写(建议实际书写),必须URL编码Value:字符串值,必须URL编码Domain:该cookie字段的有效域,可以是baidu.com域名或cdn.baidu.com子域名,缺失省值是当前页面子域名的路径:cookie字段的有效范围是指定域下的具体路径,比如cdn.baidu.com/oss,则其他路径无法访问过期时间:cookie被删除时的时间戳,默认值是在浏览器会话结束时被删除,也可以手动设置。时间格式为GMT格式Wdy,DD-Mon-YYYYHH:MM:SSGMT,可以调用Date实例的toGMTString()方法转换。如果设置为过去的时间,cookie将立即被删除。安全标志:安全一词,而不是键值对。指定后,只有在建立SSL连接时才会发送给服务器,即https协议。请注意,只有参数中的名称和值会被发送到服务器,其余只是需要浏览器识别的命令式参数。cookies的接口设置很不人性化,我们往往需要对其操作进行封装,方便使用。接下来,我们对其进行增删改查。检查cookie:decodeURIComponent(document.cookie);添加或修改cookie://需要更改为你想要的cookie和域名,路径以及是否是httpsdocument.cookie='encodeURIComponent(name)=encodeURIComponent(Leon);expries='+(newDate(Date.now()+24*60*60*1000)).toGMTString()+';路径=oss;域名=cdn.baidu.com;安全的';删除cookie:document.cookie='name=Leon;expires='+(newDate(0)).toGMTString();网上有很多具体的封装方法。函数参数最终转换为字符串。由于cookie的大小限制,需要全量传输到服务器,很多场景下存储并不适用。因此HTML5规范中出现了Storage对象,包括localStorage和sessionStorage两个继承对象,属于window属性。它提供了一个通常为5M大小的空间来保存本地数据而无需服务器交互。Storage常用方法:clear():清除所有值getItem(name):获取指定name的值key(index):获取索引值对应的keynameremoveItem(name):删除key-valuepairofthespecifiednamesetItem(name,value):为指定名称设置对应的值除了这些方法外,Storage对象还可以通过点语法或方括号语法直接访问属性和操作属性,也可以通过删除属性删除关键字。这个对象中的值都是字符串。持久化数据localStorage通常会一直保存到JS删除或用户清除浏览器缓存。会话数据sessionStorage一直保存到浏览器关闭。由于绑定会话窗口,不支持本地文件读写。另外,在IE8中,对象的读写是异步的,需要调用begin()和commit()方法来保证读取成功,这里不再赘述。存储对象的任何添加、修改或删除都会触发存储事件。该事件只支持当一个页面被修改时,另一个页面与服务器通信时会触发该事件。document.addEventListener('storage',function(e){console.log(e);});事件对象的主要属性:domain:被改变的存储空间的域名key:被修改的键名newValue:如果是setvalue,就是新值;删除则为nulloldValue:更改前的值IndexedDB有一个存储工具,可以解决很多问题,但是通常5M的大小限制还是限制了一些场景,比如后台管理系统的接口数据非常大突破5M轻而易举。这时候就需要我们的浏览器数据库IndexedDB。其实在此之前,各个厂商主要推广的是WebSQLDatabase,但后来都放弃了。虽然现在也可以在一些平台上使用,但是我们就不介绍了。IndexedDB数据库用于浏览器保存结构化数据。不同于传统的数据库,它保存的是对象,完全采用事务类型。所有的操作都转化为请求方法,所以我们需要为每一步操作添加回调函数。一个完整的例子是://判断是否可以正确打开数据库,避免多次检测letdbOpened=false;//打开本地持久化数据库,默认版本为1constrequest=indexedDB.open('jomocha');//当打开时报错request.onerror=function(event){console.error('Erroropeninglocalpersistent数据',事件);OSS.commonUI.showMsg('Openinglocalpersistentdatabaseerror,trialfunction,doesnotaffectuse,pleaseContactzhaoshuaiqiang','error');};//该版本首次创建数据库时(首次创建或更新)request.onupgradeneeded=function(event){constdb=event.target.result;//创建数据库Store对象,保存所有维度项,分为两个属性:name和listconstobjectStore=db.createObjectStore('dimensions',{keyPath:'name'});//定义存储对象的数据项属性objectStore.createIndex('name','name',{unique:true});};//成功打开数据库request.onsuccess=function(event){dbOpened=true;constdb=event.target.result;//新建一个事务,包括oncomplete和onerror处理事件,默认值为readonly,只读模式,并行consttransaction=db.transaction(['dimensions']);//打开存储对象constobjectStore=transaction.objectStore('dimensions');constrequest=objectStore.get('host');请求.onsuccess=function(event){//第一次打开数据库时,肯定没有数据,所以需要检测if(event.target.result){JomoCha.data=event.target.result.列表;}};}当数据库需要使用IndexedDB时,首先要调用indexedDB.open()方法打开数据库。如果数据存在,则发起打开请求。如果不存在,则发起创建和打开请求。此方法返回一个IDBRequest对象,可用于在此对象上添加回调方法。具体方法就是例子中最外层的request。回调函数传入的事件属性event.target指向请求,即request。如果发生错误,event.target.errorCode会保存错误信息的错误码;如果成功,event.target.result将保存一个数据库实例对象。错误码列表(第二个开始省略前缀IDBDatabaseException。):IDBDatabaseException.UNKNOWN_ERR(1):意外错误,无法归类NON_TRANSIENT_ERR(2):操作非法NOT_FOUND_ERR(3):未找到要操作的数据库CONSTRAINT_ERR(4):违反数据库约束DATA_ERR(5):提供给事务的数据不符合要求NOT_ALLOWED_ERR(6):操作非法TRANSACTION_INACTIVE_ERR(7):尝试重用已完成的事务ABORT_ERR(8):请求中止,不完整READ_ONLY_ERR(9):试图以只读方式写入或修改数据TIMEOUT_ERR(10):操作未在有效时间内完成QUOTA_ERR(11):磁盘空间不足对象存储空间(表)成功打开数据库后,我们可以打开对象存储空间,你可以把它理解为数据库中的一张表,用来保存不同的数据,比如用户、交易、购物车等。下面所有的db代表成功回调中的数据库对象。我们首先调用db.createObjectStore('dimensions',{keyPath:'name'});创建一个表,维度是唯一的表名,keyPath属性指定了表的键名,后面存储的所有数据都必须有这个属性。看写入数据库的例子://每次同步更新最新的数据$.ajax({url:'?r=tools/api/hosts',success:function(data){//如果本地可以打开数据库,然后保存if(dbOpened){constrequest=indexedDB.open('jomocha');request.onsuccess=function(event){constdb=event.target.result;//创建一个新事务,读取-write模式,不并行consttransaction=db.transaction(['dimensions'],'readwrite');//打开存储对象constobjectStore=transaction.objectStore('dimensions');//使用put方法,修改如果有的话,添加constrequest=objectStore.put({name:'host',list:data.data});}}}});这里,写入数据库的对象包含名称参数。我们获取数据并使用add()或put()方法添加数据。两种方法的区别在于,当存在相同键名时,add()报错,put()修改原值。这两个方法仍然是请求,可以为其指定onsuccess()或onerror()事件处理回调,示例中省略。事务创建数据后,就可以查询了。indexedDB中所有的读写操作都必须通过事务。我们调用db.transaction();方法打开所有存储空间(表),也可以传入参数控制我们需要打开的表://打开一个表db.transaction('user');//打开多个表db.transaction(['用户','维度']);该方法也接受第二个参数作为访问方式,包括3种类型:readonly(默认值)、readwrite和versionchange。最后一个很特别。版本更新时使用。它不能与其他事务同时执行。允许任何操作,包括删除和创建索引。现在我们已经通过事务确定了操作空间,接下来就可以拿到具体的数据对象了。下面的事务代表上面的db.transaction()方法返回的事务。一个事务可以执行多个请求,它本身就是一个请求,可以指定相应的oncomplete()和onerror()事件处理回调。oncomplete()事件无法获取事务中请求的数据。使用transaction.objectStore('dimension');获取数据对象,然后可以通过add/put/get/delete/clear方法进行增删改查,都是请求,需要设置onsuccess()和onerror()回调。游标遍历我们可以使用get()方法来获取具体的单个对象,但是如果需要遍历,就需要使用游标查询,即指定范围,然后遍历数据。下面的objectStore指的是上面transaction.objectStore()返回的数据对象集合。//指定游标范围constcursorRange=IDBKeyRange.bound('001','100');//打开游标查询constrequest=objectStore.openCursor(cursorRange);request.onsuccess=function(event){constcursor=event.目标.结果;//必须检查cursor是否存在,如果存在则为IDBCursor的实例,否则为nullif(cursor){console.log(cursor.key+':'+cursor.value);cursor.continue();}else{console.log('遍历完成');}}request.onerror=function(event){console.error('游标范围获取失败');}当游标遍历时,具体数据会保存在event.target.result中,如果item存在,则IDBCursor对象实例,否则它将为空。实例的属性:direction:value,表示游标的方向,next/nextunique/prev/prevunique,withunique会去重key:数据对象keyvalue:数据对象值primaryKey:当前游标使用的key值,后面会讲到游标中的每一个具体项时,可以使用update()和delete()对其进行修改。如果要移动光标:continue(key):如果有key,则移动到指定的item,否则移动到下一个itemadvance(count):有count移动指定数量的item,否则我们使用IDBKeyRange对象来控制上一项的键范围。有4种方式指定:only(key):只获取想要的键值对,相当于直接调用get(key)lowerBound(key,true):如果第二个参数为true,则第一个元素为key,从该项的下一项开始,默认值为falsebound(lowerkey,upperkey,true,true):前两者的组合,1和3对应lower,2和4对应upperopenCursor()方法接收第一个参数是范围区间,如果为null,则默认为整个范围;第二个参数是方向,也就是next/nextunique/prev/prevunique四个,带unique的会去重索引可以用objectStore.createIndex('name','name',{unique:true});创建一个索引,分别是索引名,索引的属性名,属性值是否唯一。调用objectStore.delete('name');删除索引。删除索引不影响数据,所以没有回调函数。如果我们不想遍历游标或者在主键上取数据,我们可以在数据集上获取一个新的索引列表://直接切换索引constindex=objectStore('dimensions');//游标遍历on索引请求=index.openCursor();如果我们想在非主键游标中找到主键值,调用index.getKey('007');并发问题数据库操作存在并发问题,但是因为是异步的,所以我们不用担心,但是如果有新的版本变更还是会出问题。因此,在打开数据库时指定onversionchange()来处理事件可以避免这个问题。setVesion()触发时,如果触发onerror(),说明数据库已经打开,现在无法更新版本,提示用户关闭其他网页,重新调用update。综上所述,如果需要与服务器进行实时交互,就使用cookies;如果需要保存一些小的信息字段,使用localStorage;如果只需要这个session有效,就用sessionStorage;如果数据很大,使用indexedDB。使用什么技术与业务场景相匹配,但技术还是需要了解和掌握。毕竟,聪明女人难做无米之炊。参考资料JavaScript高级编程第23章-离线应用和客户端存储存储事件使用:https://www.cnblogs.com/incon...IDBCursor对象:https://developer.mozilla.org...
