IndexedDB 入门
本文介绍了 HTML5 IndexedDB API 及其使用方法。
引言
HTML5 提供的新 JavaScript API 之一是 IndexedDB
API。过去,我写过一篇关于 Web Storage API 的文章,它是一个简单的键/值字典,存储在 Web 浏览器中并持久化数据。IndexedDB
是一个成熟的索引数据库,它为 Web 应用程序增加了更多的离线功能。在本文中,我将介绍 IndexedDB
API 并解释一些基本概念。
背景 - 什么是 IndexedDB?
IndexedDB
API 是浏览器中存在的索引数据库的规范。IndexedDB
由保存简单值和分层对象的记录组成。每条记录都包含一个键路径和一个对应的值,值可以是 string
或 date
等简单类型,也可以是 JavaScript 对象和数组等更高级的类型。它可以包含用于更快检索记录的索引,并且可以存储大量的对象。
IndexedDB
有两种 API 模式——同步和异步。大多数情况下,您将使用异步 API。同步 API 仅用于与 Web Workers 结合使用(并且目前大多数浏览器都不支持)。
IndexedDB
API 通过 window.indexedDB
对象公开。该 API 目前尚未被大多数浏览器完全支持。支持该 API 的主要浏览器以其前缀公开 indexedDB
对象。目前在使用 indexedDB
之前应使用以下代码行,并且您应该使用 Modernizr
等库来检测浏览器是否支持该 API
var indexedDB = window.indexedDB || window.webkitIndexedDB ||
window.mozIndexedDB || window.msIndexedDB;
在撰写本文时,IndexedDB 受到 Firefox 4 及更高版本(Firefox 目前是与规范相关更新最快的浏览器)、Chrome 11 及更高版本和 IE10 的支持。
使用 IndexedDB
API,您可以使 Web 应用程序离线,并减少服务器往返次数,因为您可以将常用数据存储在本地数据库中,而不是服务器数据库中。
打开数据库
在使用 IndexedDB
之前,您首先需要打开数据库以供使用。由于 IndexedDB
采用异步工作方式,调用 open
函数将返回一个 IDBRequest
对象,您将使用该对象来绑定成功和错误事件处理程序。以下是打开数据库的示例
var db;
var request = indexedDB.open("TestDatabase");
request.onerror = function(evt) {
console.log("Database error code: " + evt.target.errorCode);
};
request.onsuccess = function(evt) {
db = request.result;
};
在该示例中,调用 open
函数用于打开名为 TestDatabase
的数据库。调用后,两个回调函数绑定到返回的 IDBRequest
,一个用于 onerror
,一个用于 onsuccess
。在成功回调中,您可以从请求中获取数据库对象并将其存储以供后续使用。
open
函数接受另一个示例中未传递的参数,即数据库的版本号。版本号用于更改数据库的版本。如果数据库的版本小于提供的版本,将触发 upgradeneeded
事件,您可以在其处理程序中更改数据库的结构。更改数据库版本**是**更改数据库结构的唯一方法。
创建对象存储
IndexedDB
可以包含一个或多个 objectStores
。objectStores
类似于关系数据库中的表,但又与它们大不相同。它们包含键/值记录,并且可以具有键路径、键生成器和索引。您使用 IndexedDB
的 createObjectStore
函数来创建 objectStore
。该函数获取 objectStore
的名称和一个选项对象来配置键路径和键生成器等。
键路径和键生成器用于为存储的值创建主索引。键路径是一个 string
,它定义了如何从给定值中提取键。它与具有与键路径名称完全相同的属性的 JavaScript 对象一起使用。如果不存在具有完全相同名称的属性,则需要提供一个键生成器,例如 autoIncrement
。键生成器用于保存任何类型的值。它将自动为您生成一个键,但如果需要,您也可以为存储的值传递自己的键。
objectStores
还可以包含索引,这些索引稍后将用于数据检索。索引是使用 objectStore
的 createIndex
函数创建的,该函数可以接受三个参数——索引的名称、要创建索引的属性名称和一个选项对象。
以下是在 onupdateneeded
事件处理程序中创建 objectStore
的示例
var peopleData = [
{ name: "John Dow", email: "john@company.com" },
{ name: "Don Dow", email: "don@company.com" }
];
function initDb() {
var request = indexedDB.open("PeopleDB", 1);
request.onsuccess = function (evt) {
db = request.result;
};
request.onerror = function (evt) {
console.log("IndexedDB error: " + evt.target.errorCode);
};
request.onupgradeneeded = function (evt) {
var objectStore = evt.currentTarget.result.createObjectStore("people",
{ keyPath: "id", autoIncrement: true });
objectStore.createIndex("name", "name", { unique: false });
objectStore.createIndex("email", "email", { unique: true });
for (i in peopleData) {
objectStore.add(peopleData[i]);
}
};
}
该示例展示了一些重要事项
onupdateneeded
在onsuccess
回调之前调用,因此您可以使用evt.currentTarget.result
获取正在打开的数据库。- 键路径是使用提供的对象中不存在的
id string
创建的。键路径与autoIncrement
选项结合使用,以创建递增的键生成器。 - 您可以在索引上使用唯一约束来强制执行简单约束。当唯一选项为
true
时,索引将对插入的电子邮件强制执行约束。 - 您可以使用
objectStore
的add
函数向objectStore
添加记录。
创建事务
当您拥有一个 objectStore
时,您可能希望将其用于 CRUD(创建/读取/更新/删除)操作。在 IndexedDB
中使用 CRUD 的唯一方法是通过 IDBTransaction
对象。目前,IDBTransaction
也支持浏览器前缀(像 IndexedDB
对象一样),因此应该使用以下代码行
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;
IDBTransaction
对象可以以三种模式创建:只读、读/写和快照。您应该只在需要更新 objectStores
时使用读/写模式,在其他情况下使用只读模式。原因在于只读事务可以并发运行。默认情况下,事务以只读模式运行。
事务与所有其他 IndexedDB
API 调用一样是异步的。这意味着您可以为它们的 error
、abort
和 complete
事件绑定处理程序。以下是打开 add
事务的示例
var transaction = db.transaction("people", IDBTransaction.READ_WRITE);
var objectStore = transaction.objectStore("people");
var request = objectStore.add({ name: name, email: email });
request.onsuccess = function (evt) {
// do something when the add succeeded
};
该示例显示,您首先为人员 objectStore
创建一个 transaction
对象。然后,您从 transaction
对象中检索 objectStore
并在其上执行操作。该操作是异步调用的,因此您绑定一个 onsuccees
事件处理程序来处理请求的成功。在该示例中,我没有绑定任何事务事件处理程序,但您可以像以下示例一样使用它们
transaction.oncomplete = function(evt) {
// do something after the transaction completed
};
检索数据
为了从 objectStore
中检索数据,您将使用 transaction
对象以及 objectStore
的 get
函数。get
函数需要一个值,该值将用于与 objectStore
的键路径进行匹配。以下是使用 get
函数的示例
var transaction = db.transaction("people");
var objectStore = transaction.objectStore("people");
var request = objectStore.get(1);
request.onsuccess = function(evt) {
alert("Name for id 1 " + request.result.name);
};
检索数据的另一种方法是使用 cursor
。当您不知道键路径时,您将使用游标。Cursor
s 是针对属于 transaction
的 objectStore
打开的。以下是使用 cursor
的示例
var transaction = db.transaction("people", IDBTransaction.READ_WRITE);
var objectStore = transaction.objectStore("people");
var request = objectStore.openCursor();
request.onsuccess = function(evt) {
var cursor = evt.target.result;
if (cursor) {
output.textContent += "id: " + cursor.key + " is " + cursor.value.name + " ";
cursor.continue();
}
else {
console.log("No more entries!");
}
};
在该示例中,针对 objectStore
调用 openCursor
函数。然后,一个 onsuccess
函数绑定到 cursor
请求,并用于将 cursor
检索到的数据写入一个名为 output 的 div
。前面的示例是一个非常简单的 cursor
示例。Cursor
s 可以用于更复杂的查询,本文中不会展示这些查询。
完整示例
以下是一些 IndexedDB
概念的完整示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>IndexedDB</title>
<script type="text/javascript">
var indexedDB = window.indexedDB || window.webkitIndexedDB ||
window.mozIndexedDB || window.msIndexedDB;
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;
var db;
(function () {
var peopleData = [
{ name: "John Dow", email: "john@company.com" },
{ name: "Don Dow", email: "don@company.com" }
];
function initDb() {
var request = indexedDB.open("PeopleDB", 1);
request.onsuccess = function (evt) {
db = request.result;
};
request.onerror = function (evt) {
console.log("IndexedDB error: " + evt.target.errorCode);
};
request.onupgradeneeded = function (evt) {
var objectStore = evt.currentTarget.result.createObjectStore(
"people", { keyPath: "id", autoIncrement: true });
objectStore.createIndex("name", "name", { unique: false });
objectStore.createIndex("email", "email", { unique: true });
for (i in peopleData) {
objectStore.add(peopleData[i]);
}
};
}
function contentLoaded() {
initDb();
var btnAdd = document.getElementById("btnAdd");
var btnDelete = document.getElementById("btnDelete");
var btnPrint = document.getElementById("btnPrint");
btnAdd.addEventListener("click", function () {
var name = document.getElementById("txtName").value;
var email = document.getElementById("txtEmail").value;
var transaction = db.transaction("people", IDBTransaction.READ_WRITE);
var objectStore = transaction.objectStore("people");
var request = objectStore.add({ name: name, email: email });
request.onsuccess = function (evt) {
// do something after the add succeeded
};
}, false);
btnDelete.addEventListener("click", function () {
var id = document.getElementById("txtID").value;
var transaction = db.transaction("people", IDBTransaction.READ_WRITE);
var objectStore = transaction.objectStore("people");
var request = objectStore.delete(id);
request.onsuccess = function(evt) {
// It's gone!
};
}, false);
btnPrint.addEventListener("click", function () {
var output = document.getElementById("printOutput");
output.textContent = "";
var transaction = db.transaction("people", IDBTransaction.READ_WRITE);
var objectStore = transaction.objectStore("people");
var request = objectStore.openCursor();
request.onsuccess = function(evt) {
var cursor = evt.target.result;
if (cursor) {
output.textContent += "id: " + cursor.key +
" is " + cursor.value.name + " ";
cursor.continue();
}
else {
console.log("No more entries!");
}
};
}, false);
}
window.addEventListener("DOMContentLoaded", contentLoaded, false);
})();
</script>
</head>
<body>
<div id="container">
<label for="txtName">
Name:
</label>
<input type="text" id="txtName" name="txtName" />
<br />
<label for="txtEmail">
Email:
</label>
<input type="email" id="txtEmail" name="txtEmail" />
<br />
<input type="button" id="btnAdd" value="Add Record" />
<br />
<label for="txtID">
ID:
</label>
<input type="text" id="txtID" name="txtID" />
<br />
<input type="button" id="btnDelete" value="Delete Record" />
<br />
<input type="button" id="btnPrint" value="Print objectStore" />
<br />
<output id="printOutput">
</output>
</div>
</body>
</html>
IndexedDB 和 Web Storage API
如引言所述,浏览器中有两种数据存储——IndexedDB
和 Web Storage。我经常听到的一个问题是为什么要使用两种不同的存储类型?在需要键/值对且数据量非常小的简单场景中,Web Storage 比 IndexedDB
更适合,并且可以简化您的工作。另一方面,在您需要高效搜索值或需要在客户端存储大量对象的场景中,IndexedDB
更可取。这两种选项相互补充,可以在同一个应用程序中一起使用。
摘要
IndexedDB
包含一个庞大的 API,用于在浏览器中使用内置索引数据库。它可用于在客户端存储数据,并与 Web Storage 一起提供离线应用程序的机会,并减少数据检索的服务器往返次数。有关 IndexedDB
的更多信息,您可以访问此链接中的规范。