前言
Indexed Database API 简称 IndexedDB,是浏览器中存储结构化数据的一个方案。IndexedDB 用于代
替目前已废弃的 Web SQL Database API。IndexedDB 背后的思想是创造一套 API,方便 JavaScript 对象的
存储和获取,同时也支持查询和搜索。
IndexedDB 的设计几乎完全是异步的。为此,大多数操作以请求的形式执行,这些请求会异步执行,
产生成功的结果或错误。绝大多数 IndexedDB 操作要求添加 onerror 和 onsuccess 事件处理程序来确
定输出。
2017 年,新发布的主流浏览器(Chrome、Firefox、Opera、Safari)完全支持 IndexedDB。IE10/11
和 Edge 浏览器部分支持 IndexedDB。
特点
- 1 键值对储存。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
- 2 异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
- 3 支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
- 4 同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
- 5 储存空间大 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
- 6 支持二进制储存。 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
数据库
IndexedDB 是类似于 MySQL 或 Web SQL Database 的数据库。与传统数据库最大的区别在于,
IndexedDB 使用对象存储而不是表格保存数据。IndexedDB 数据库就是在一个公共命名空间下的一组对
象存储,类似于 NoSQL 风格的实现。
使用 IndexedDB 数据库的第一步是调用 indexedDB.open()方法,并给它传入一个要打开的数据
库名称。如果给定名称的数据库已存在,则会发送一个打开它的请求;如果不存在,则会发送创建并打
开这个数据库的请求。这个方法会返回 IDBRequest 的实例,可以在这个实例上添加 onerror 和
onsuccess 事件处理程序
let db,
request,
version = 1;
request = indexedDB.open("admin", version);
request.onerror = (event) =>
alert(`Failed to open: ${event.target.errorCode}`);
request.onsuccess = (event) => {
db = event.target.result;
};
在两个事件处理程序中,event.target 都指向 request,因此使用哪个都可以。如果 onsuccess
事件处理程序被调用,说明可以通过 event.target.result 访问数据库(IDBDatabase)实例了,
这个实例会保存到 db 变量中。之后,所有与数据库相关的操作都要通过 db 对象本身来进行。如果打
开数据库期间发生错误,event.target.errorCode 中就会存储表示问题的错误码。
对象存储
建立了数据库连接之后,下一步就是使用对象存储。如果数据库版本与期待的不一致,那可能需要
创建对象存储。不过,在创建对象存储前,有必要想一想要存储什么类型的数据。
假设要存储包含用户名、密码等内容的用户记录。可以用如下对象来表示一条记录:
let user = {
username: "007",
firstName: "James",
lastName: "Bond",
password: "foo"
};
观察这个对象,可以很容易看出最适合作为对象存储键的 username 属性。用户名必须全局唯一,
它也是大多数情况下访问数据的凭据。这个键很重要,因为创建对象存储时必须指定一个键。
数据库的版本决定了数据库模式,包括数据库中的对象存储和这些对象存储的结构。如果数据库还
不存在,open()操作会创建一个新数据库,然后触发 upgradeneeded 事件。可以为这个事件设置处
理程序,并在处理程序中创建数据库模式。如果数据库存在,而你指定了一个升级版的版本号,则会立
即触发 upgradeneeded 事件,因而可以在事件处理程序中更新数据库模式。
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 如果存在则删除当前 objectStore。测试的时候可以这样做
// 但这样会在每次执行事件处理程序时删除已有数据
if (db.objectStoreNames.contains("users")) {
db.deleteObjectStore("users");
}
db.createObjectStore("users", { keyPath: "username" });
};
这里第二个参数的 keyPath 属性表示应该用作键的存储对象的属性名。
事务
创建了对象存储之后,剩下的所有操作都是通过事务完成的。事务要通过调用数据库对象的
transaction()方法创建。任何时候,只要想要读取或修改数据,都要通过事务把所有修改操作组织
起来,
注意request.onsuccess和 request.onupgradeneeded 都是异步得,所以下方得增删改查 都需要 在异步里操作,不然回报找不到 db.transaction
let db;
var request = indexedDB.open('users');
request.onerror = function (event) {
console.log('数据库打开报错');
};
request.onsuccess = function (event) {
db = event.target.result;
};
request.onupgradeneeded = function(event) {
db = event.target.result;
var objectStore;
if (!db.objectStoreNames.contains('person')) {
objectStore = db.createObjectStore('person', { keyPath: 'id' });
}else{
objectStore = db.createObjectStore('person', { keyPath: 'id' });
}
objectStore.createIndex('name', 'name', { unique: false });
objectStore.createIndex('age', 'age', { unique: false });
};
写操作
function addData() {
console.log(db)
var request = db.transaction(['person'], 'readwrite') //readwrite表示有读写权限
.objectStore('person')
.add({ id: 1, name: 'iwhao', age: 18}); //新增数据
request.onsuccess = function (event) {
console.log('数据写入成功');
};
request.onerror = function (event) {
console.log('数据写入失败');
}
}
读取操作
function read() {
var transaction = db.transaction(['person']);
var objectStore = transaction.objectStore('person');
var request = objectStore.get(1);
request.onerror = function(event) {
console.log('事务失败');
};
request.onsuccess = function( event) {
if (request.result) {
console.log(request.result);
} else {
console.log('未获得数据记录');
}
};
}
更新
function upData() {
console.log(db)
var request = db.transaction(['person'], 'readwrite') //readwrite表示有读写权限
.objectStore('person')
.put({ id: 1, name: 'whao', age: 20}); //更新数据
request.onsuccess = function (event) {
console.log('数据写入成功');
};
request.onerror = function (event) {
console.log('数据写入失败');
}
}
删除
function del(){
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.delete(1);
request.onsuccess = function (event) {
console.log('数据删除成功');
};
}
索引
对某些数据集,可能需要为对象存储指定多个键。例如,如果同时记录了用户 ID 和用户名,那可能
需要通过任何一种方式来获取用户数据。为此,可以考虑将用户 ID 作为主键,然后在用户名上创建索引。
假定新建表格的时候,对name字段建立了索引。
objectStore.createIndex('name', 'name', { unique: false });
createIndex()的第一个参数是索引的名称,第二个参数是索引属性的名称,第三个参数是包含
键 unique 的 options 对象。这个选项中的 unique 应该必须指定,表示这个键是否在所有记录中唯
一。因为 username 可能不会重复,所以这个键是唯一的。
索引查询
function getIndexes() {
const transaction = db.transaction(['person']);
const store = transaction.objectStore('person');
const index = store.index("name")
const request = index.get("iwhao");
request.onerror = function(event) {
console.log('事务失败');
};
request.onsuccess = function( event) {
if (request.result) {
console.log(request.result);
} else {
console.log('未获得数据记录');
}
};
}
限制
IndexedDB 的很多限制实际上与 Web Storage 一样。首先,IndexedDB 数据库是与页面源(协议、域
和端口)绑定的,因此信息不能跨域共享。这意味着 www.wrox.com 和 p2p.wrox.com 会对应不同的数据
存储。
其次,每个源都有可以存储的空间限制。当前 Firefox 的限制是每个源 50MB,而 Chrome 是 5MB。
移动版 Firefox 有 5MB 限制,如果用度超出配额则会请求用户许可。
Firefox 还有一个限制——本地文本不能访问 IndexedDB 数据库。Chrome 没有这个限制。因此在本
地运行本书示例时,要使用 Chrome。