HTML5 的一個(gè)重要特性是本地?cái)?shù)據(jù)持久性,它使用戶能夠在線和離線訪問(wèn) Web 應(yīng)用程序。此外,本地?cái)?shù)據(jù)持久性使移動(dòng)應(yīng)用程序更靈敏,使用的帶寬更少,而且能夠在低帶寬場(chǎng)景中更高效地工作。HTML5 提供了一些本地?cái)?shù)據(jù)持久性選項(xiàng)。第一個(gè)選項(xiàng)是 localstorage,它支持您使用一個(gè)簡(jiǎn)單的鍵值對(duì)來(lái)存儲(chǔ)數(shù)據(jù)。IndexedDB(一個(gè)更加強(qiáng)大的選項(xiàng))支持您本地存儲(chǔ)大量對(duì)象,并使用健壯的數(shù)據(jù)訪問(wèn)機(jī)制檢索數(shù)據(jù)。
IndexedDB API 取代了 Web Storage API,后者在 HTML5 規(guī)范中已不推薦使用。(但一些領(lǐng)先的瀏覽器仍然支持 Web Storage,其中包括蘋果公司的 Safari 和 Opera Web 瀏覽器)與 Web Storage 相比,IndexedDB 具有多個(gè)優(yōu)勢(shì),其中包括索引、事務(wù)處理和健壯的查詢功能。本文將通過(guò)一系列的示例來(lái)展示如何管理 IndexedDB 數(shù)據(jù)庫(kù)。(參見(jiàn) 下載 一節(jié),獲取示例的完整源代碼。)
重要概念
一個(gè)網(wǎng)站可能有一個(gè)或多個(gè) IndexedDB 數(shù)據(jù)庫(kù),每個(gè)數(shù)據(jù)庫(kù)必須具有惟一的名稱。
一個(gè)數(shù)據(jù)庫(kù)可包含一個(gè)或多個(gè)對(duì)象存儲(chǔ)。一個(gè)對(duì)象存儲(chǔ)(由一個(gè)名稱惟一標(biāo)識(shí))是一個(gè)記錄集合。每個(gè)記錄有一個(gè)鍵 和一個(gè)值。該值是一個(gè)對(duì)象,可擁有一個(gè)或多個(gè)屬性。鍵可能基于某個(gè)鍵生成器,從一個(gè)鍵路徑衍生出來(lái),或者是顯式設(shè)置。一個(gè)鍵生成器自動(dòng)生成惟一的連續(xù)正整數(shù)。鍵路徑定義了鍵值的路徑。它可以是單個(gè) JavaScript 標(biāo)識(shí)符或多個(gè)由句點(diǎn)分隔的標(biāo)識(shí)符。
規(guī)范中包含一個(gè)異步 API 和一個(gè)同步 API。同步 API 用于 Web 瀏覽器中。異步 API 使用請(qǐng)求和回調(diào)。
在以下示例中,輸出附加到一個(gè)具有 ID result 的 div 標(biāo)記上。要更新 result 元素,可在每個(gè)數(shù)據(jù)操作期間清除并設(shè)置 innerHTML 屬性。每個(gè)示例 JavaScript 函數(shù)由 HTML 按鈕的一個(gè) onclick 事件調(diào)用。
處理錯(cuò)誤或異常和調(diào)試
所有異步請(qǐng)求都有一個(gè) onsuccess 回調(diào)和一個(gè) onerror 回調(diào),前者在數(shù)據(jù)庫(kù)操作成功時(shí)調(diào)用,后者在一個(gè)操作未成功時(shí)調(diào)用。清單 1 是一個(gè) onerror 回調(diào)的示例。
清單 1. 異步錯(cuò)誤處理函數(shù)
request.onerror = function(e) {
// handle error
...
console.log("Database error: " + e.target.errorCode);
};
在使用 IndexedDB API 時(shí),使用 JavaScript try/catch 塊是一個(gè)不錯(cuò)的想法。此功能對(duì)處理可能在數(shù)據(jù)庫(kù)操作之前發(fā)生的錯(cuò)誤和異常很有用,比如在數(shù)據(jù)庫(kù)未打開(kāi)時(shí)嘗試讀取或操作數(shù)據(jù),或者在另一個(gè)讀/寫事務(wù)已打開(kāi)時(shí)嘗試寫入數(shù)據(jù)。
IndexedDB 很難調(diào)試和排除故障,因?yàn)樵谠S多情況下,錯(cuò)誤消息是泛泛的,缺乏信息價(jià)值。在開(kāi)發(fā)應(yīng)用程序時(shí),可以使用 console.log 和 JavaScript 調(diào)試工具,比如用于 Mozilla Firefox 的 Firebug,或者 Chrome 內(nèi)置的 Developer Tools。對(duì)于任何 JavaScript 密集型應(yīng)用程序,這些工具的價(jià)值是無(wú)可估量的,它們尤其適用于使用 IndexedDB API 的 HTML5 應(yīng)用程序。
使用數(shù)據(jù)庫(kù)
一個(gè)數(shù)據(jù)庫(kù)一次只能有一個(gè)版本。在首次創(chuàng)建數(shù)據(jù)庫(kù)時(shí),它的初始版本編號(hào)為 0。創(chuàng)建數(shù)據(jù)庫(kù)之后,數(shù)據(jù)庫(kù)(和它的對(duì)象存儲(chǔ))只能通過(guò)一種稱為 versionchange 的特殊類型的事務(wù)來(lái)更改。要在創(chuàng)建數(shù)據(jù)庫(kù)后更改它,必須打開(kāi)具有更高版本的數(shù)據(jù)庫(kù)。此操作會(huì)觸發(fā) upgradeneeded 事件。修改數(shù)據(jù)庫(kù)或?qū)ο蟠鎯?chǔ)的代碼必須位于 upgradeneeded 事件處理函數(shù)中。
清單 2 中的代碼段展示了如何創(chuàng)建數(shù)據(jù)庫(kù):調(diào)用 open 方法并傳遞數(shù)據(jù)庫(kù)名稱。如果不存在具有指定名稱的數(shù)據(jù)庫(kù),則會(huì)創(chuàng)建該數(shù)據(jù)庫(kù)。
清單 2. 創(chuàng)建一個(gè)新的數(shù)據(jù)庫(kù)
function createDatabase() {
var openRequest = localDatabase.indexedDB.open(dbName);
openRequest.onerror = function(e) {
console.log("Database error: " + e.target.errorCode);
};
openRequest.onsuccess = function(event) {
console.log("Database created");
localDatabase.db = openRequest.result;
};
openRequest.onupgradeneeded = function (evt) {
...
};
}
要?jiǎng)h除現(xiàn)有數(shù)據(jù)庫(kù),可以調(diào)用 deleteDatabase 方法,并傳遞要?jiǎng)h除的數(shù)據(jù)庫(kù)名稱,如 清單 3 中所示。
清單 3. 刪除現(xiàn)有數(shù)據(jù)庫(kù)
function deleteDatabase() {
var deleteDbRequest = localDatabase.indexedDB.deleteDatabase(dbName);
deleteDbRequest.onsuccess = function (event) {
// database deleted successfully
};
deleteDbRequest.onerror = function (e) {
console.log("Database error: " + e.target.errorCode);
};
}
清單 4 中的代碼段展示了如何打開(kāi)與現(xiàn)有數(shù)據(jù)庫(kù)的連接。
清單 4. 打開(kāi)數(shù)據(jù)庫(kù)的最新版本
function openDatabase() {
var openRequest = localDatabase.indexedDB.open("dbName");
openRequest.onerror = function(e) {
console.log("Database error: " + e.target.errorCode);
};
openRequest.onsuccess = function(event) {
localDatabase.db = openRequest.result;
};
}
創(chuàng)建、刪除和打開(kāi)數(shù)據(jù)庫(kù)就是這么簡(jiǎn)單。現(xiàn)在是時(shí)候使用對(duì)象存儲(chǔ)了。
使用對(duì)象存儲(chǔ)
對(duì)象存儲(chǔ)是一個(gè)數(shù)據(jù)記錄集合。要在現(xiàn)有數(shù)據(jù)庫(kù)中創(chuàng)建一個(gè)新對(duì)象存儲(chǔ),則需要對(duì)現(xiàn)有數(shù)據(jù)庫(kù)進(jìn)行版本控制。為此,請(qǐng)打開(kāi)要進(jìn)行版本控制的數(shù)據(jù)庫(kù)。除了數(shù)據(jù)庫(kù)名稱之外,open 方法還接受版本號(hào)作為第二個(gè)參數(shù)。如果希望創(chuàng)建數(shù)據(jù)庫(kù)的一個(gè)新版本(也就是說(shuō),要?jiǎng)?chuàng)建或修改一個(gè)對(duì)象存儲(chǔ)),只需打開(kāi)具有現(xiàn)有數(shù)據(jù)庫(kù)版本更高的數(shù)據(jù)庫(kù)。這會(huì)調(diào)用 onupgradeneeded 事件處理函數(shù)。
要?jiǎng)?chuàng)建一個(gè)對(duì)象存儲(chǔ),可以在數(shù)據(jù)庫(kù)對(duì)象上調(diào)用 createObjectStore 方法,如 清單 5 中所示。
清單 5. 創(chuàng)建對(duì)象存儲(chǔ)
function createObjectStore() {
var openRequest = localDatabase.indexedDB.open(dbName, 2);
openRequest.onerror = function(e) {
console.log("Database error: " + e.target.errorCode);
};
openRequest.onsuccess = function(event) {
localDatabase.db = openRequest.result;
};
openRequest.onupgradeneeded = function (evt) {
var employeeStore = evt.currentTarget.result.createObjectStore
("employees", {keyPath: "id"});
};
}
我們已經(jīng)了解了對(duì)象存儲(chǔ)的工作原理。接下來(lái),讓我們看看索引 如何引用包含數(shù)據(jù)的對(duì)象存儲(chǔ)。
使用索引
除了使用鍵來(lái)檢索對(duì)象存儲(chǔ)中的記錄,還可使用代索引的字段來(lái)檢索記錄。對(duì)象存儲(chǔ)可具有一個(gè)或多個(gè)索引。索引是一種特殊的對(duì)象存儲(chǔ),它引用包含數(shù)據(jù)的對(duì)象存儲(chǔ),在更改所引用的對(duì)象存儲(chǔ)時(shí)(也就是添加、修改或刪除記錄時(shí))自動(dòng)更新。
要?jiǎng)?chuàng)建一個(gè)索引,必須使用 清單 5 中所示的方法對(duì)數(shù)據(jù)庫(kù)進(jìn)行版本控制。索引可以是惟一的,也可以是不惟一的。惟一索引要求索引中的所有值都是惟一的,比如使用一個(gè)電子郵件地址字段。當(dāng)某個(gè)值可以重復(fù)出現(xiàn)時(shí),需要使用非惟一索引,比如城市、州或國(guó)家。清單 6 中的代碼段展示了如何在 employee 對(duì)象的 state 字段上創(chuàng)建一個(gè)非惟一索引,在 ZIP code 字段上創(chuàng)建一個(gè)非惟一索引,并在 email address 字段上創(chuàng)建一個(gè)惟一索引:
清單 6. 創(chuàng)建索引
function createIndex() {
var openRequest = localDatabase.indexedDB.open(dbName, 2);
openRequest.onerror = function(e) {
console.log("Database error: " + e.target.errorCode);
};
openRequest.onsuccess = function(event) {
db = openRequest.result;
};
openRequest.onupgradeneeded = function (evt) {
var employeeStore = evt.currentTarget.result.objectStore("employees");
employeeStore.createIndex("stateIndex", "state", { unique: false });
employeeStore.createIndex("emailIndex", "email", { unique: true });
employeeStore.createIndex("zipCodeIndex", "zip_code", { unique: false })
};
}
接下來(lái),您將使用事務(wù)對(duì)對(duì)象存儲(chǔ)執(zhí)行一些操作。
使用事務(wù)
您需要使用事務(wù)在對(duì)象存儲(chǔ)上執(zhí)行所有讀取和寫入操作。類似于關(guān)系數(shù)據(jù)庫(kù)中的事務(wù)的工作原理,IndexedDB 事務(wù)提供了數(shù)據(jù)庫(kù)寫入操作的一個(gè)原子集合,這個(gè)集合要么完全提交,要么完全不提交。IndexedDB 事務(wù)還擁有數(shù)據(jù)庫(kù)操作的一個(gè)中止和提交工具。
表 1 列出并描述了 IndexedDB 提供的事務(wù)模式。
表 1. IndexedDB 事務(wù)模式
默認(rèn)的事務(wù)模式為 readonly。您可在任何給定時(shí)刻打開(kāi)多個(gè)并發(fā)的 readonly 事務(wù),但只能打開(kāi)一個(gè) readwrite 事務(wù)。出于此原因,只有在數(shù)據(jù)更新時(shí)才考慮使用 readwrite 事務(wù)。單獨(dú)的(表示不能打開(kāi)任何其他并發(fā)事務(wù))versionchange 事務(wù)操作一個(gè)數(shù)據(jù)庫(kù)或?qū)ο蟠鎯?chǔ)。可以在 onupgradeneeded 事件處理函數(shù)中使用 versionchange 事務(wù)創(chuàng)建、修改或刪除一個(gè)對(duì)象存儲(chǔ),或者將一個(gè)索引添加到對(duì)象存儲(chǔ)。
要在 readwrite 模式下為 employees 對(duì)象存儲(chǔ)創(chuàng)建一個(gè)事務(wù),可以使用語(yǔ)句:var transaction = db.transaction("employees", "readwrite");。
清單 7 中的 JavaScript 函數(shù)展示了如何使用一個(gè)事務(wù),使用鍵來(lái)檢索 employees 對(duì)象存儲(chǔ)中的一條特定的員工記錄。
清單 7. 使用鍵獲取一個(gè)特定的記錄
function fetchEmployee() {
try {
var result = document.getElementById("result");
result.innerHTML = "";
if (localDatabase != null && localDatabase.db != null) {
var store = localDatabase.db.transaction("employees").objectStore("employees");
store.get("E3").onsuccess = function(event) {
var employee = event.target.result;
if (employee == null) {
result.value = "employee not found";
}
else {
var jsonStr = JSON.stringify(employee);
result.innerHTML = jsonStr;
}
};
}
}
catch(e){
console.log(e);
}
}
清單 8 中的 JavaScript 函數(shù)展示了如何使用一個(gè)事務(wù),以使用 emailIndex 索引而不是對(duì)象存儲(chǔ)鍵來(lái)檢索 employees 對(duì)象存儲(chǔ)中的特定員工記錄。
清單 8. 使用索引獲取特定的記錄
function fetchEmployeeByEmail() {
try {
var result = document.getElementById("result");
result.innerHTML = "";
if (localDatabase != null && localDatabase.db != null) {
var range = IDBKeyRange.only("john.adams@somedomain.com");
var store = localDatabase.db.transaction("employees")
.objectStore("employees");
var index = store.index("emailIndex");
index.get(range).onsuccess = function(evt) {
var employee = evt.target.result;
var jsonStr = JSON.stringify(employee);
result.innerHTML = jsonStr;
};
}
}
catch(e)
【網(wǎng)站聲明】本站除付費(fèi)源碼經(jīng)過(guò)測(cè)試外,其他素材未做測(cè)試,不保證完整性,網(wǎng)站上部分源碼僅限學(xué)習(xí)交流,請(qǐng)勿用于商業(yè)用途。如損害你的權(quán)益請(qǐng)聯(lián)系客服QQ:2655101040 給予處理,謝謝支持。