25.2 Web Storage
Web Storage的目的:解决通过客户端存储不需要频繁发送回服务器的数据时使用cookie的问题。
Web Storage规范主要有两个目标:
❑ 提供在cookie之外的存储会话数据的途径;
❑ 提供跨会话持久化存储大量数据的机制。
Web Storage的第2版定义了两个对象:localStorage和sessionStorage。
localStorage是永久存储机制,sessionStorage是跨会话的存储机制。
这两种浏览器存储API提供了在浏览器中不受页面刷新影响而存储数据的两种方式。
25.2.1 Storage类型
Storage类型用于保存名/值对数据,直至存储空间上限(由浏览器决定)。
Storage的实例与其他对象一样,但增加了以下方法:
❑ clear():删除所有值;不在Firefox中实现。
❑ getItem(name):取得给定name的值。
❑ key(index):取得给定数值位置的名称。
❑ removeItem(name):删除给定name的名/值对。
❑ setItem(name, value):设置给定name的值。
建议使用方法而非属性来执行这些操作,以免意外重写某个已存在的对象成员。
通过length属性可以确定Storage对象中保存了多少名/值对。
注:Storage类型只能存储字符串。非字符串数据在存储之前会自动转换为字符串。注意,这种转换不能在获取数据时撤销。
25.2.2 sessionStorage对象
sessionStorage对象只存储会话数据,这意味着数据只会存储到浏览器关闭。
存储在sessionStorage中的数据不受页面刷新影响,可以在浏览器崩溃并重启后恢复。(取决于浏览器,Firefox和WebKit支持,IE不支持。)
限制:
因为sessionStorage对象与服务器会话紧密相关,所以在运行本地文件时不能使用。
存储在sessionStorage对象中的数据只能由最初存储数据的页面使用,在多页应用程序中的用处有限。
因为sessionStorage对象是Storage的实例,所以可通过使用setItem()方法或直接给属性赋值给它添加数据。
对存在于sessionStorage上的数据,可以使用getItem()或直接访问属性名来取得。
可结合sessionStorage的length属性和key()方法遍历所有的值。
也可以使用for-in循环迭代sessionStorage的值。
for (let i = 0, len = sessionStorage.length; i < len; i++) {let key = sessionStorage.key(i); // 通过key()先取得给定位置中的数据名称let value = sessionStorage.getItem(key); // 然后使用该名称通过getItem()取得值console.len(`${key} = ${value}`); // 可以依次访问sessionStorage中的名/值对}for (let key in sessionStorage) {let value = sessionStorage.getItem(key);console.len(`${key} = ${value}`);}// 每次循环,key都会被赋予sessionStorage中的一个名称;// 这里不会返回内置方法或length属性。
sessionStorage中删除数据:(2种方法)
delete操作符直接删除对象属性;
removeItem()方法;
delete sessionStorage.name; // delete删除sessionStorage.removeItem('book'); // 方法删除
sessionStorage对象应该主要用于存储只在会话期间有效的小块数据。
如果需要跨会话持久存储数据,可以使用globalStorage或localStorage。
25.2.3 localStorage对象
要访问同一个localStorage对象,页面必须来自同一个域(子域不可以)、在相同的端口上使用相同的协议。
因为localStorage是Storage的实例,所以可以像使用sessionStorage一样使用localStorage。
两种存储方法的区别在于:
存储在localStorage中的数据,会保留到通过JavaScript删除或者用户清除浏览器缓存。
localStorage数据不受页面刷新影响,也不会因关闭窗口、标签页或重新启动浏览器而丢失。
25.2.4 存储事件
每当Storage对象发生变化时,都会在文档上触发storage事件。
使用属性或setItem()设置值、使用delete或removeItem()删除值,以及每次调用clear()时都会触发这个事件。
这个事件的事件对象有如下4个属性:
❑ domain:存储变化对应的域。
❑ key:被设置或删除的键。
❑ newValue:键被设置的新值,若键被删除则为null。
❑ oldValue:键变化之前的值。
可以使用如下代码监听storage事件:
window.addEventListener('storage',(event) => console.log(`Storage changed for ${event.domain}`));
25.2.5 限制
具体的限制取决于特定的浏览器。
一般来说,客户端数据的大小限制是按照每个源(协议、域和端口)来设置的,因此每个源有固定大小的数据存储空间。
分析存储数据的页面的源可以加强这一限制。
不同浏览器给localStorage和sessionStorage设置了不同的空间限制,但大多数会限制为每个源5MB。
关于每种媒介的新配额限制信息表,可以参考web.dev网站上的文章“Storage for the Web”。
要了解关于Web Storage限制的更多信息,可以参考dev-test.nemikor网站的“Web Storage Support Test”页面。
25.3 IndexedDB
Indexed Database API简称IndexedDB,是浏览器中存储结构化数据的一个方案。
IndexedDB用于代替目前已废弃的Web SQL Database API。
IndexedDB背后的思想是创造一套API,方便JavaScript对象的存储和获取,同时也支持查询和搜索。
IndexedDB的设计几乎完全是异步的。
为此,大多数操作以请求的形式执行,这些请求会异步执行,产生成功的结果或错误。
绝大多数IndexedDB操作要求添加onerror和onsuccess事件处理程序来确定输出。
25.3.1 数据库
IndexedDB是类似于MySQL或Web SQL Database的数据库。
与传统数据库最大的区别在于:
IndexedDB使用对象存储而不是表格保存数据。
IndexedDB数据库就是在一个公共命名空间下的一组对象存储,类似于NoSQL风格的实现。
使用IndexedDB数据库的第一步是:
调用indexedDB.open()方法,并给它传入一个要打开的数据库名称。
如果给定名称的数据库已存在,则会发送一个打开它的请求;
如果不存在,则会发送创建并打开这个数据库的请求。
这个方法会返回IDBRequest的实例,可以在这个实例上添加onerror和onsuccess事件处理程序。
25.3.2 对象存储
建立了数据库连接之后,下一步就是使用对象存储。
如果数据库版本与期待的不一致,那可能需要创建对象存储。
不过,在创建对象存储前,有必要想一想要存储什么类型的数据。
25.3.3 事务创建
了对象存储之后,剩下的所有操作都是通过事务完成的。
事务要通过调用数据库对象的transaction()方法创建。
任何时候,只要想要读取或修改数据,都要通过事务把所有修改操作组织起来。
25.3.4 插入对象
拿到了对象存储的引用后,可用add()或put()写入数据。
这两个方法都接收一个参数,即要存储的对象,并把对象保存到对象存储。
这两个方法只在对象存储中已存在同名的键时有区别。
这种情况下,add()会导致错误,而put()会简单地重写该对象。
更简单地说,可以把add()想象成插入新值,而把put()想象为更新值。
25.3.5 通过游标查询
使用事务可通过一个已知键取得一条记录。如果想取得多条数据,则需要在事务中创建一个游标。
游标是一个指向结果集的指针。
与传统数据库查询不同,游标不会事先收集所有结果。相反,游标指向第一个结果,并在接到指令前不会主动查找下一条数据。
需要在对象存储上调用openCursor()方法创建游标。
与其他IndexedDB操作一样,openCursor()方法也返回一个请求,因此必须为它添加onsuccess和onerror事件处理程序。
25.3.6 键范围
使用游标会给人一种不太理想的感觉,因为获取数据的方式受到了限制。
使用键范围(key range)可以让游标更容易管理。
键范围对应IDBKeyRange的实例。
有四种方式指定键范围:
第一种是使用only()方法并传入想要获取的键;
第二种键范围可以定义结果集的下限。下限表示游标开始的位置。
第三种键范围可以定义结果集的上限,通过调用upperBound()方法可以指定游标不会越过的记录。
25.3.7 设置游标方向
openCursor()方法实际上可以接收两个参数,第一个是IDBKeyRange的实例,第二个是表示方向的字符串。
通常,游标都是从对象存储的第一条记录开始,每次调用continue()或advance()都会向最后一条记录前进。这样的游标其默认方向为”next”。
如果对象存储中有重复的记录,可能需要游标跳过那些重复的项。
25.3.8 索引
对某些数据集,可能需要为对象存储指定多个键。
例如,如果同时记录了用户ID和用户名,那可能需要通过任何一种方式来获取用户数据。
为此,可以考虑将用户ID作为主键,然后在用户名上创建索引。
要创建新索引,首先要取得对象存储的引用。
25.3.9 并发问题
IndexedDB虽然是网页中的异步API,但仍存在并发问题。
如果两个不同的浏览器标签页同时打开了同一个网页,则有可能出现一个网页尝试升级数据库,而另一个尚未就绪的情形。
有问题的操作是:设置数据库为新版本,而版本变化只能在浏览器只有一个标签页使用数据库时才能完成。
第一次打开数据库时,添加onversionchange事件处理程序非常重要。
另一个同源标签页将数据库打开到新版本时,将执行此回调。
对这个事件最好的回应是立即关闭数据库,以便完成版本升级。
25.3.10 限制
有三个:
IndexedDB的很多限制实际上与Web Storage一样。
① IndexedDB数据库是与页面源(协议、域和端口)绑定的,因此信息不能跨域共享。
这意味着www.wrox.com和p2p.wrox.com会对应不同的数据存储。
② 每个源都有可以存储的空间限制。
当前Firefox的限制是每个源50MB,而Chrome是5MB。移动版Firefox有5MB限制,如果用度超出配额则会请求用户许可。
③ Firefox还有一个限制——本地文本不能访问IndexedDB数据库。Chrome没有这个限制。因此在本地运行本书示例时,要使用Chrome。
