IndexedDB 是一种可以让你在用户的浏览器内持久化存储数据的方法。IndexedDB 为生成 Web Application 提供了丰富的查询能力,使我们的应用在在线和离线时都可以正常工作。
IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 使用索引实现对数据的高性能搜索。虽然 Web Storage 在存储较少量的数据很有用,但对于存储更大量的结构化数据来说力不从心。而 IndexedDB 提供了这种场景的解决方案。
基于 window.indexedDB
API
基本使用流程
IndexedDB 基本使用流程如下所示:
- 打开数据库
.open
。 - 在数据库中创建一个对象仓库(object store)
.createObjectStore
。 - 启动一个事务
.transaction
,并发送一个请求来执行一些数据库操作,像增加或提取数据等。 - 通过监听正确类型的 DOM 事件以等待操作完成。
- 在操作结果上进行一些操作(可以在 request 对象中找到)
基本使用
新增IndexedDB数据库
声明一个创建IndexedDB请求
window.indexedDB.open(db_name:string, version:integer)
IndexedDB声明请求接收三种不同的 DOM 事件:success
(声明成功)、error
(声明失败) 和 onupgradeneeded
(版本更新)
var db_name = 'the_name'
var db
var request = window.indexedDB.open(db_name, 1)
request.onerror = function(event) {
// Do something with request.errorCode!
alert("Database error: " + event.target.errorCode);
};
request.onsuccess = function(event) {
// Do something with request.result!
db = event.target.result
};
// 更新版本,用来创建或者删除对象存储仓库
request.onupgradeneeded = function(event) {
// 保存 IDBDataBase 接口
db = event.target.result;
// 为该数据库创建一个对象存储仓库
var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });
// 创建对象支持索引
objectStore.createIndex('name', 'name', { unique: false });
objectStore.createIndex('emial', 'emial', { unique: true });
};
新增IndexedDB对象存储仓库
使用 db.createObjectStore(name:string, opt:obj)
创建一个对象存储仓库,需要在onupgradeneeded
监听中实现,否则会报错提示
opt 支持:
- keyPath:string :使用键路径,指定主键索引名称,键值唯一值
- autoIncrement:boolean : 使用键生成器,默认该设置是不开启的,从1开始,默认加1
声明完对象存储仓库,使用 store.createIndex(name, key, opt:{unique:boolean})
声明对象索引,通过unique
指明键值是否唯一
// 该事件仅在较新的浏览器中实现了
request.onupgradeneeded = function(event) {
// 保存 IDBDataBase 接口
db = event.target.result;
// 为该数据库创建一个对象存储仓库
var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });
// 创建对象支持索引
objectStore.createIndex('name', 'name', { unique: false });
objectStore.createIndex('emial', 'emial', { unique: true });
};
};
使用事务(transaction)添加键值对
需要使用事务(transaction)才能对IndexedDB的对象存储进行操作
使用 db.transaction(stores:array, mode:string)
声明一个事务
支持三种模式 mode:
- 'readonly':对象仓库只读,默认
- 'readwrite':对象仓库支持读写
- 'versionchange':支持新建或删除对象仓库或索引
事务接收三种不同的 DOM 事件:error
、abort
和 complete
使用 transaction.objectStore(stores:array)
读取对象存储仓库,只能从创建事务时指定的对象仓库中取出一个对象仓库
读取对象存储如截图
// 我们的客户数据看起来像这样。
const customerData = [
{ ssn: "444-44-4444", name: "Bill", age: 35, email: "bill@company.com" },
{ ssn: "555-55-5555", name: "Donna", age: 32, email: "donna@home.org" }
];
// 声明一个事务
var transaction = db.transaction(["customers"], "readwrite");
// 在所有数据添加完毕后的处理
transaction.oncomplete = function(event) {
alert("All done!");
};
transaction.onerror = function(event) {
// 不要忘记错误处理!
};
// 获取对象存储仓库,执行添加
var objectStore = transaction.objectStore("customers");
customerData.forEach(function(customer) {
var request = objectStore.add(customer);
request.onsuccess = function(event) {
// event.target.result === customer.ssn;
};
});
使用事务(transaction)获取键值对
使用 store.get(key)
获取指定主键值(Key)的对象
var request = db.transaction(["customers"], "readwrite")
.objectStore("customers")
.get("444-44-4444");
request.onsuccess = function(event) {
// 读取成功!
alert("Name for SSN 444-44-4444 is " + request.result.name);
};
使用事务(transaction)编辑键值对
同添加,只要主键一样,则替换新值,或者删除后,重新添加
使用事务(transaction)删除对象索引
使用 store.deleteIndex(keyPath)
删除对象索引
var request = db.transaction(["customers"], "versionchange")
.objectStore("customers")
.deleteIndex("email");
request.onsuccess = function(event) {
// 删除成功!
};
使用事务(transaction)删除键值对
使用 store.delete(key)
删除指定主键值(Key)的对象
var request = db.transaction(["customers"], "readwrite")
.objectStore("customers")
.delete("444-44-4444");
request.onsuccess = function(event) {
// 删除成功!
};
使用事务(transaction)删除所有键值对
使用 store.clear()
删除仓库所有键值对
使用游标
如果你想要遍历对象存储空间中的所有值,那么你可以使用游标。
使用 store.openCursor()
打开一个游标,通过 .continue()
移动游标
var objectStore = db.transaction("customers").objectStore("customers");
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
alert("Name for SSN " + cursor.key + " is " + cursor.value.name);
cursor.continue();
}
else {
alert("No more entries!");
}
};
openCursor 支持参数:
- range:IDBKeyRange 限制被检索的项目的范围
- direction:IDBCursor 光标移动的方向
使用索引
使用 store..index(key)
获取指定索引名称的IDBIndex对象
读取数据如截图
可以在索引上打开两个不同类型的游标:
- 一个常规游标映射索引属性到对象存储空间中的对象
- 一个键索引映射索引属性到用来存储对象存储空间中的对象的键
不同之处被展示如下:
// 获取索引name 的索引路径
var index = objectStore.index("name");
index.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// cursor.key 是一个 name, 就像 "Bill", 然后 cursor.value 是整个对象。
alert("Name: " + cursor.key + ", SSN: " + cursor.value.ssn + ", email: " + cursor.value.email);
cursor.continue();
}
};
index.openKeyCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// cursor.key 是一个 name, 就像 "Bill", 然后 cursor.value 是那个 SSN。
// 没有办法可以得到存储对象的其余部分。
alert("Name: " + cursor.key + ", SSN: " + cursor.value);
cursor.continue();
}
};
指定索引范围
// 仅匹配 "Donna"
var singleKeyRange = IDBKeyRange.only("Donna");
// 匹配所有超过“Bill”的,包括“Bill”
var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill");
// 匹配所有超过“Bill”的,但不包括“Bill”
var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true);
// 匹配所有不超过“Donna”的,但不包括“Donna”
var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true);
// 匹配所有在“Bill”和“Donna”之间的,但不包括“Donna”
var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true);
// 使用其中的一个键范围,把它作为 openCursor()/openKeyCursor 的第一个参数
index.openCursor(boundKeyRange).onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// 当匹配时进行一些操作
cursor.continue();
}
};
指定游标方向
// 指定范围,游标方向
objectStore.openCursor(boundKeyRange, "prev").onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// 进行一些操作
cursor.continue();
}
};
// 只改变游标方向,不限制范围,则第一参数传null
objectStore.openCursor(null, "prev").onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// Do something with the entries.
cursor.continue();
}
};
当一个 web app 在另一个标签页中被打开时的版本变更
当你的网页应用以数据库版本变更的方式发生改变时,你需要考虑,如果用户在一个标签页中打开的应用里使用了旧版本的数据库,在另一个标签页里加载新版本的数据库时会发生什么。当你使用更高的版本号调用 open()
方法时,其他所有打开的数据库必须显式地确认请求,你才能对数据库进行修改(onblocked
事件会被触发知道它们被关闭或重新加载):
var request = mozIndexedDB.open("MyTestDatabase", 2);
request.onblocked = function(event) {
// 如果其他的一些页签加载了该数据库,在我们继续之前需要关闭它们
alert("请关闭其他由该站点打开的页签!");
};
request.onupgradeneeded = function(event) {
// 其他的数据已经被关闭,一切就绪
db.createObjectStore(/* ... */);
useDatabase(db);
};
request.onsuccess = function(event) {
var db = event.target.result;
useDatabase(db);
return;
};
function useDatabase(db) {
// 当由其他页签请求了版本变更时,确认添加了一个会被通知的事件处理程序。
// 这里允许其他页签来更新数据库,如果不这样做,版本升级将不会发生知道用户关闭了这些页签。
db.onversionchange = function(event) {
db.close();
alert("A new version of this page is ready. Please reload or close this tab!");
};
// 处理数据库
}
安全
IndexedDB 使用同源原则,这意味着它把存储空间绑定到了创建它的站点的源(典型情况下,就是站点的域或是子域),所以它不能被任何其他源访问。
第三方窗口内容(比如 <iframe>
内容)可以访问它所嵌入的源的 IndexedDB 仓库,除非浏览器被设置成从不接受第三方 cookies(参见 bug 1147821)。
IndexedDB检测
配合DevTools 的Application Panel 传送门