摘要:在前端开发过程中,为了与服务器更方便的交互或者提升用户体验,我们都会在客户端用户本地保存一部分数据,比如。这篇文章的客户端本地存储,我们主要讲到四种技术。回调函数传入的事件属性就指向该请求,即。删除索引不会影响数据,所以没有回调函数。
在前端开发过程中,为了与服务器更方便的交互或者提升用户体验,我们都会在客户端(用户)本地保存一部分数据,比如cookie/localStorage/sessionStorage。在后端管理系统的前端,更是会涉及到一部分超大数据的请求,一个接口有时会达到5M甚至15M的程度,当这个接口数据并不是经常更新时,我们可以用两种方式,一种是分页请求+预加载+懒加载,另一种就是本地存储+热更新。而由于第二种方式用户体验更优秀,便是我常用的方式。
这篇文章的客户端本地存储,我们主要讲到cookie/localStorage/sessionStorage/indexedDB四种技术。
cookieHTTP Cookie通常简称cookie,该标准用于浏览器存储会话信息,在发起HTTP请求时携带Cookie参数:
// Request Header GET /oss/index.php?r=api/jlog/collect HTTP/1.1 Host: gzhxy.baidu.com:8090 Connection: keep-alive Pragma: no-cache Cache-Control: no-cache X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36 ··· Cookie: name=Leon; age=24
Cookie有一些限制:
它是以; (分号+一个空格)分割的键值对字符串,在网络传送时必须是URL编码的。
绑定在固定域名下,不允许其它域名访问。
有些浏览器有数量限制,在超出后删除顺序不统一,有些最近最少使用(LRU),有些随机删
同一域名下总大小限制5KB,超出限制后静默失败
Cookie的参数构成:
名称:唯一确定,不区分大小写(实际编写建议区分),必须URL编码
值:字符串值,必须URL编码
域:该cookie字段有效的域,可以为baidu.com域名,也可以为cdn.baidu.com子域名,缺省值为当前页面子域名
路径:该cookie字段有效范围为指定域下的具体路径,如cdn.baidu.com/oss,那么其他路径就无法访问
失效时间:cookie被删除的时间戳,缺省值为浏览器会话结束被删除,也可以手动设置,时间格式为GMT格式Wdy, DD-Mon-YYYY HH:MM:SS GMT,可以调用Date实例的toGMTString()方法转换,如果设置为过去的时间,该cookie被立刻删除
安全标志:为单词secure而非键值对,指定后,只有在SSL连接的时候才会被发送到服务器,也就是https协议
注意参数中只有名称和值才会被发送给服务器,其余的只是需要浏览器识别的命令式参数。
cookie的接口设置非常的不人性化,往往需要我们对其操作进行封装才会方便使用。下面我们对其进行增删查改。
查看cookie:decodeURIComponent(document.cookie);
添加或修改cookie:
// 需要改成自己需要的cookie和域名、路径以及是否为https document.cookie = "encodeURIComponent(name)=encodeURIComponent(Leon); expries=" + (new Date(Date.now() + 24*60*60*1000)).toGMTString() + "; path=oss; domain=cdn.baidu.com; secure";
删除cookie: document.cookie = "name=Leon; expires=" + (new Date(0)).toGMTString();
具体的封装的方式网上有很多,可以去搜一搜,核心就是对cookie进行字符串检索和切分,以及将传入的函数参数最终转换为字符串。
Storage由于cookie的大小限制和需要全量传递给服务器,在很多场景下并不适用,所以HTML5规范中出现了Storage对象,包含localStorage和sessionStorage两种继承对象,属于window的属性。它提供了通常5M的大小空间来保存无需服务器交互的本地数据。
Storage的常用方法:
clear(): 清除所有值
getItem(name): 获取指定name的值
key(index): 获得对应索引值的键名
removeItem(name): 删除指定name的键值对
setItem(name, value): 为指定name设置对应的值
除了这些方法之外,Storage对象可以直接通过点语法或者方括号语法访问属性和操作属性,也可以通过delete关键字删除属性。该对象中的值均为字符串。
持久化数据localStorage通常保留到JS删除或者用户清除浏览器缓存。
会话数据sessionStorage保留到关闭浏览器。由于绑定会话窗口,所以不支持本地文件读写。另外在IE8中,该对象为异步读写,需要调用begin()和commit()方法保证成功读取,不再赘述。
对Storage对象做任何的新增,修改或者删除操作,都会触发storage事件。该事件只支持在与服务端通信时,一个页面修改,另一个页面会触发该事件。
document.addEventListener("storage", function (e) { console.log(e); });
该事件对象的主要属性:
domain: 发生变化的存储空间的域名
key:修改的键名
newValue:如果是设置值,则为新值;如果删除,则为null
oldValue:更改前的值
IndexedDB拥有了Storage利器,已经能解决很多问题,但是通常5M的大小限制还是会限制一部分场景,比如后台管理系统的接口数据很容易突破5M,这个时候就需要我们的浏览器数据库IndexedDB了。其实在此之前,各家厂商主要推广的是Web SQL Database,不过后来被废弃了,虽然现在在部分平台上也能使用,但我们不做介绍了。
IndexedDB数据库用于浏览器保存结构化数据,区别于传统数据库它保存的是对象,完全采用事务类型,所有的操作被转换为请求的方式,所以我们需要对每一步操作添加回调函数。
一个完整的实例为:
// 判断能否正确打开数据库,避免多次检测 let dbOpened = false; // 打开本地持久化数据库,默认版本为1 const request = indexedDB.open("jomocha"); // 当打开错误时 request.onerror = function(event){ console.error("打开本地持久化数据错误", event); OSS.commonUI.showMsg("打开本地持久化数据库错误,试用功能,不影响使用,请联系zhaoshuaiqiang", "error"); }; // 当数据库首次创建该版本时(首次创建或更新版本) request.onupgradeneeded = function(event){ const db = event.target.result; // 创建一个数据库存储对象,保存所有的维度项,分为name和list两个属性 const objectStore = db.createObjectStore("dimensions", { keyPath: "name" }); // 定义存储对象的数据项属性 objectStore.createIndex("name", "name", { unique: true }); }; // 成功打开了数据库 request.onsuccess = function(event){ dbOpened = true; const db = event.target.result; // 新建一个事务,包含oncomplete 和onerror句柄事件,缺省值为readonly,只读模式,可并行 const transaction = db.transaction(["dimensions"]); // 打开存储对象 const objectStore = transaction.objectStore("dimensions"); const request = objectStore.get("host"); request.onsuccess = function (event) { // 第一次打开数据库时,肯定没有数据,所以需要检测 if (event.target.result) { JomoCha.data = event.target.result.list; } }; }数据库
当我们需要使用IndexedDB时,首先要调用indexedDB.open()方法打开数据库,如果该数据存在,则发起打开的请求,如果不存在,则发起创建并打开的请求。该方法会返回一个IDBRequest对象,可以在该对象上添加回调方法。具体的方法如示例中的最外层请求。
回调函数传入的事件属性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"});来创建一张表,这个dimensions为唯一表名,其中的keyPath属性指定该表的键名,后面存储的所有数据都必须拥有该属性。
看下写入数据库的实例:
// 每次同步更新最新数据 $.ajax({ url: "?r=tools/api/hosts", success: function (data) { // 如果能够打开本地数据库,则保存 if (dbOpened) { const request = indexedDB.open("jomocha"); request.onsuccess = function(event){ const db = event.target.result; // 新建一个事务,读写模式,不可并行 const transaction = db.transaction(["dimensions"], "readwrite"); // 打开存储对象 const objectStore = transaction.objectStore("dimensions"); // 使用put方法,有则修改,没有则添加 const request = objectStore.put({ name: "host", list: data.data }); } } } });
这里写入数据库的对象里就包含了name参数。我们拿到数据使用add()或put()方法添加数据,这两种方法区别在于遇到相同的键名存在时,add()报错,put()修改原有值。这两种方法依然为请求,可以对其指定onsuccess()或onerror()事件处理回调,示例中省略了。
事务在创建完成数据后,就可以对其进行查询了,indexedDB中所有读写操作都要通过事务,我们调用db.transaction();方法来打开所有存储空间(表),也可以传入参数来控制我们需要打开的表:
// 打开一张表 db.transaction("user"); // 打开多张表 db.transaction(["user", "dimensions"]);
该方法还接受第二个参数作为访问方式,包含3种:readonly(缺省值)、readwrite、versionchange。最后一个比较特别,为在版本更新时使用,不可以与其他事务并发执行,允许任何操作,包括删除和创建索引。
现在通过事务我们已经确定了操作空间,接下来就可以获取具体的数据对象了,下面的transaction代表着上面的db.transaction()方法返回的事务。事务可以执行多个请求,本身也是一个请求,可以指定相应的oncomplete()和onerror()事件处理回调。其中的oncomplete()事件不能拿到该事务中请求的数据。
使用transaction.objectStore("dimension");获取数据对象,然后就可以通过add/put/get/delete/clear方法进行增删查改,均为请求,需要设置onsuccess()和onerror()回调。
游标遍历我们可以使用get()方法检索出具体的单个对象,但是如果需要遍历,我们就要使用到游标查询了,也就是指定范围,然后遍历数据。下面的objectStore指代上面transaction.objectStore()返回的数据对象集。
// 指定游标范围 const cursorRange = IDBKeyRange.bound("001", "100"); // 打开游标查询 const request = objectStore.openCursor(cursorRange); request.onsuccess = function (event) { const cursor = event.target.result; // 必须检查cursor是否存在,如果该项存在,则为IDBCursor实例,否则为null if (cursor) { console.log(cursor.key + ": " + cursor.value); cursor.continue(); } else { console.log("遍历完成"); } } request.onerror = function (event) { console.error("游标区间获取失败"); }
在游标遍历时,具体的数据会保存在event.target.result里,如果该项存在,则会为一个IDBCursor对象实例,否则为null。实例的属性:
direction:数值,表示游标的方向,next/nextunique/prev/prevunique,带有unique的会去重
key:数据对象键
value:数据对象值
primaryKey:游标当前的使用键值,后面会说
在遍历到游标中具体的每一项时,可以使用update()和delete()来修改,如果想要移动游标:
continue(key):存在key移动到指定项,否则下一项
advance(count):存在count移动指定项数,否则上一项
我们通过IDBKeyRange对象来控制键范围,有4种方式指定:
only(key):只取得想要的键值对,等同于直接调用get(key)
lowerBound(key, true):第一个元素为key,如果第二个参数为true,从该项的下一项开始,缺省值为false
upperBound(key, true):最后一个元素为key,如果第二个参数为true,从该项的下一项开始,缺省值为false
bound(lowerkey, upperkey, true, true):前两者的结合,1和3对应lower,2和4对应upper
openCursor()方法接收的第一个参数为范围区间,如果为null,则默认全部范围;第二个参数为方向,为next/nextunique/prev/prevunique四个,带有unique的会去重
索引可以使用objectStore.createIndex("name", "name", {unique: true});来创建索引,分别为索引名,索引的属性名,该属性值是否唯一。调用objectStore.delete("name");来删除索引。删除索引不会影响数据,所以没有回调函数。
如果我们不想在主键上遍历游标或者获取数据,可以在数据集上取得新的索引列表:
// 直接切换索引 const index = objectStore("dimensions"); // 在该索引上进行游标遍历 request = index.openCursor();
如果我们在非主键的游标中,想要去的主键值,调用index.getKey("007");
并发问题数据库操作存在并发问题,但由于是异步,我们不用担心,但是如果存在新版本变更还是会导致问题。所以打开数据库时指定onversionchange()处理事件,可以避免这个问题,在setVesion()时,如果触发onerror()代表已经打开了该数据库,无法现在更新版本,提示用户关闭其它网页,重新调用更新。
总结如果需要与服务器实时交互,使用cookie,如果需要保存一些小信息字段,使用localStorage,如果只需要本次会话有效,使用sessionStorage,如果数据很大,使用indexedDB。使用什么技术跟业务场景匹配,但是技术还是都要了解都要会,毕竟,巧妇难为无米之炊。
参考资料JavaScript高级程序设计 23章-离线应用与客户端存储
storage事件使用:https://www.cnblogs.com/incon...
IDBCursor对象:https://developer.mozilla.org...
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/92454.html
摘要:私有缓存就是用户专享的,各级代理不能缓存的缓存。代表使用内存中的缓存,则代表使用的是硬盘中的缓存,浏览器读取缓存的顺序为。 作为一名前端工作人员,前端的缓存知识是必须掌握的,因为一个网站打开网页的速度直接关系到用户体验,用户粘度,而提高网页的打开速度有很多方面需要优化,其中比较重要的一点就是利用好缓存,缓存文件可以重复利用,还可以减少带宽,降低网络负荷。 1 缓存 缓存从宏观上分为私有...
摘要:私有缓存就是用户专享的,各级代理不能缓存的缓存。代表使用内存中的缓存,则代表使用的是硬盘中的缓存,浏览器读取缓存的顺序为。 作为一名前端工作人员,前端的缓存知识是必须掌握的,因为一个网站打开网页的速度直接关系到用户体验,用户粘度,而提高网页的打开速度有很多方面需要优化,其中比较重要的一点就是利用好缓存,缓存文件可以重复利用,还可以减少带宽,降低网络负荷。 1 缓存 缓存从宏观上分为私有...
摘要:私有缓存就是用户专享的,各级代理不能缓存的缓存。代表使用内存中的缓存,则代表使用的是硬盘中的缓存,浏览器读取缓存的顺序为。 作为一名前端工作人员,前端的缓存知识是必须掌握的,因为一个网站打开网页的速度直接关系到用户体验,用户粘度,而提高网页的打开速度有很多方面需要优化,其中比较重要的一点就是利用好缓存,缓存文件可以重复利用,还可以减少带宽,降低网络负荷。 1 缓存 缓存从宏观上分为私有...
阅读 902·2021-11-22 13:53
阅读 2532·2021-10-15 09:40
阅读 999·2021-10-14 09:42
阅读 3474·2021-09-22 15:59
阅读 887·2021-09-02 09:47
阅读 2365·2019-08-30 15:54
阅读 1437·2019-08-29 17:14
阅读 398·2019-08-29 15:15