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的值。

  1. for (let i = 0, len = sessionStorage.length; i < len; i++) {
  2. let key = sessionStorage.key(i); // 通过key()先取得给定位置中的数据名称
  3. let value = sessionStorage.getItem(key); // 然后使用该名称通过getItem()取得值
  4. console.len(`${key} = ${value}`); // 可以依次访问sessionStorage中的名/值对
  5. }
  6. for (let key in sessionStorage) {
  7. let value = sessionStorage.getItem(key);
  8. console.len(`${key} = ${value}`);
  9. }
  10. // 每次循环,key都会被赋予sessionStorage中的一个名称;
  11. // 这里不会返回内置方法或length属性。

sessionStorage中删除数据:(2种方法)
delete操作符直接删除对象属性;
removeItem()方法;

  1. delete sessionStorage.name; // delete删除
  2. 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事件:

  1. window.addEventListener('storage',
  2. (event) => console.log(`Storage changed for ${event.domain}`)
  3. );

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。