65.9K
CodeProject 正在变化。 阅读更多。
Home

IndexedDB 入门

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (31投票s)

2012 年 2 月 5 日

CPOL

7分钟阅读

viewsIcon

191388

downloadIcon

2239

本文介绍了 HTML5 IndexedDB API 及其使用方法。

引言

HTML5 提供的新 JavaScript API 之一是 IndexedDB API。过去,我写过一篇关于 Web Storage API 的文章,它是一个简单的键/值字典,存储在 Web 浏览器中并持久化数据。IndexedDB 是一个成熟的索引数据库,它为 Web 应用程序增加了更多的离线功能。在本文中,我将介绍 IndexedDB API 并解释一些基本概念。

背景 - 什么是 IndexedDB?

IndexedDB API 是浏览器中存在的索引数据库的规范。IndexedDB 由保存简单值和分层对象的记录组成。每条记录都包含一个键路径和一个对应的值,值可以是 stringdate 等简单类型,也可以是 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 可以包含一个或多个 objectStoresobjectStores 类似于关系数据库中的表,但又与它们大不相同。它们包含键/值记录,并且可以具有键路径、键生成器和索引。您使用 IndexedDBcreateObjectStore 函数来创建 objectStore。该函数获取 objectStore 的名称和一个选项对象来配置键路径和键生成器等。

键路径和键生成器用于为存储的值创建主索引。键路径是一个 string,它定义了如何从给定值中提取键。它与具有与键路径名称完全相同的属性的 JavaScript 对象一起使用。如果不存在具有完全相同名称的属性,则需要提供一个键生成器,例如 autoIncrement。键生成器用于保存任何类型的值。它将自动为您生成一个键,但如果需要,您也可以为存储的值传递自己的键。

objectStores 还可以包含索引,这些索引稍后将用于数据检索。索引是使用 objectStorecreateIndex 函数创建的,该函数可以接受三个参数——索引的名称、要创建索引的属性名称和一个选项对象。

以下是在 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]);
        }
    };
}

该示例展示了一些重要事项

  • onupdateneededonsuccess 回调之前调用,因此您可以使用 evt.currentTarget.result 获取正在打开的数据库。
  • 键路径是使用提供的对象中不存在的 id string 创建的。键路径与 autoIncrement 选项结合使用,以创建递增的键生成器。
  • 您可以在索引上使用唯一约束来强制执行简单约束。当唯一选项为 true 时,索引将对插入的电子邮件强制执行约束。
  • 您可以使用 objectStoreadd 函数向 objectStore 添加记录。

创建事务

当您拥有一个 objectStore 时,您可能希望将其用于 CRUD(创建/读取/更新/删除)操作。在 IndexedDB 中使用 CRUD 的唯一方法是通过 IDBTransaction 对象。目前,IDBTransaction 也支持浏览器前缀(像 IndexedDB 对象一样),因此应该使用以下代码行

var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;

IDBTransaction 对象可以以三种模式创建:只读、读/写和快照。您应该只在需要更新 objectStores 时使用读/写模式,在其他情况下使用只读模式。原因在于只读事务可以并发运行。默认情况下,事务以只读模式运行。

事务与所有其他 IndexedDB API 调用一样是异步的。这意味着您可以为它们的 errorabortcomplete 事件绑定处理程序。以下是打开 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 对象以及 objectStoreget 函数。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。当您不知道键路径时,您将使用游标。Cursors 是针对属于 transactionobjectStore 打开的。以下是使用 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 示例。Cursors 可以用于更复杂的查询,本文中不会展示这些查询。

完整示例

以下是一些 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>
请注意 – 此示例仅适用于 Firefox 10,因为 Firefox 10 是目前唯一更新了 IndexedDB API 实现以使用最新规范版本的浏览器。

IndexedDB 和 Web Storage API

如引言所述,浏览器中有两种数据存储——IndexedDB 和 Web Storage。我经常听到的一个问题是为什么要使用两种不同的存储类型?在需要键/值对且数据量非常小的简单场景中,Web Storage 比 IndexedDB 更适合,并且可以简化您的工作。另一方面,在您需要高效搜索值或需要在客户端存储大量对象的场景中,IndexedDB 更可取。这两种选项相互补充,可以在同一个应用程序中一起使用。

摘要

IndexedDB 包含一个庞大的 API,用于在浏览器中使用内置索引数据库。它可用于在客户端存储数据,并与 Web Storage 一起提供离线应用程序的机会,并减少数据检索的服务器往返次数。有关 IndexedDB 的更多信息,您可以访问此链接中的规范。

© . All rights reserved.