標籤:
本機資料持久性提高了 Web 應用程式可訪問性和行動裝置 App程式響應能力
索引資料庫 (IndexedDB) API(作為 HTML5 的一部分)對建立具有豐富本機存放區資料的資料密集型的離線 HTML5 Web 應用程式很有用。同時它還有助於本機快取資料,使傳統線上 Web 應用程式(比如移動 Web 應用程式)能夠更快地運行和響應。本文將介紹如何管理 IndexedDB 資料庫。
HTML5 的一個重要特性是本機資料持久性,它使使用者能夠線上和離線訪問 Web 應用程式。此外,本機資料持久性使行動裝置 App程式更靈敏,使用的頻寬更少,而且能夠在低頻寬情境中更高效地工作。HTML5 提供了一些本機資料持久性選項。第一個選項是 localstorage
,它支援您使用一個簡單的索引值對來儲存資料。IndexedDB(一個更加強大的選項)支援您本機存放區大量對象,並使用健壯的資料訪問機制檢索資料。
IndexedDB API 取代了 Web Storage API,後者在 HTML5 規範中已不推薦使用。(但一些領先的瀏覽器仍然支援 Web Storage,其中包括蘋果公司的 Safari 和 Opera 網頁瀏覽器)與 Web Storage 相比,IndexedDB 具有多個優勢,其中包括索引、交易處理和健壯的查詢功能。本文將通過一系列的樣本來展示如何管理 IndexedDB 資料庫。(參見 下載 一節,擷取樣本的完整原始碼。)
重要概念
一個網站可能有一個或多個 IndexedDB 資料庫,每個資料庫必須具有惟一的名稱。
一個資料庫可包含一個或多個Object Storage Service。一個Object Storage Service(由一個名稱惟一標識)是一個記錄集合。每個記錄有一個鍵 和一個值。該值是一個對象,可擁有一個或多個屬性。鍵可能基於某個鍵產生器,從一個鍵路徑衍生出來,或者是顯式設定。一個鍵產生器自動產生惟一的連續正整數。鍵路徑定義了索引值的路徑。它可以是單個 JavaScript 標識符或多個由句點分隔的標識符。
規範中包含一個非同步 API 和一個同步 API。同步 API 用於 網頁瀏覽器中。非同步 API 使用請求和回調。
在以下樣本中,輸出附加到一個具有 ID result
的 div
標記上。要更新 result
元素,可在每個資料操作期間清除並設定 innerHTML
屬性。每個樣本 JavaScript 函數由 HTML 按鈕的一個 onclick
事件調用。
處理錯誤或異常和調試
所有非同步請求都有一個 onsuccess
回調和一個 onerror
回調,前者在資料庫操作成功時調用,後者在一個操作未成功時調用。清單 1 是一個onerror
回調的樣本。
清單 1. 非同步錯誤處理函數
request.onerror = function(e) { // handle error ... console.log("Database error: " + e.target.errorCode);};
在使用 IndexedDB API 時,使用 JavaScript try/catch
塊是一個不錯的想法。此功能對處理可能在資料庫操作之前發生的錯誤和異常很有用,比如在資料庫未開啟時嘗試讀取或操作資料,或者在另一個讀/寫事務已開啟時嘗試寫入資料。
IndexedDB 很難調試和排除故障,因為在許多情況下,錯誤訊息是泛泛的,缺乏資訊價值。在開發應用程式時,可以使用 console.log
和 JavaScript 調試工具,比如用於 Mozilla Firefox 的 Firebug,或者 Chrome 內建的 Developer Tools。對於任何 JavaScript 密集型應用程式,這些工具的價值是無可估量的,它們尤其適用於使用 IndexedDB API 的 HTML5 應用程式。
回頁首
使用資料庫
一個資料庫一次只能有一個版本。在首次建立資料庫時,它的初始版本編號為 0。建立資料庫之後,資料庫(和它的Object Storage Service)只能通過一種稱為 versionchange
的特殊類型的事務來更改。要在建立資料庫後更改它,必須開啟具有更高版本的資料庫。此操作會觸發 upgradeneeded
事件。修改資料庫或Object Storage Service的代碼必須位於 upgradeneeded
事件處理函數中。
清單 2 中的程式碼片段展示了如何建立資料庫:調用 open
方法並傳遞資料庫名稱。如果不存在具有指定名稱的資料庫,則會建立該資料庫。
清單 2. 建立一個新的資料庫
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) { ... };}
要刪除現有資料庫,可以調用 deleteDatabase
方法,並傳遞要刪除的資料庫名稱,如 清單 3 中所示。
清單 3. 刪除現有資料庫
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 中的程式碼片段展示了如何開啟與現有資料庫的串連。
清單 4. 開啟資料庫的最新版本
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; };}
建立、刪除和開啟資料庫就是這麼簡單。現在是時候使用Object Storage Service了。
回頁首
使用Object Storage Service
Object Storage Service是一個資料記錄集合。要在現有資料庫中建立一個新Object Storage Service,則需要對現有資料庫進資料列版本設定。為此,請開啟要進資料列版本設定的資料庫。除了資料庫名稱之外,open
方法還接受版本號碼作為第二個參數。如果希望建立資料庫的一個新版本(也就是說,要建立或修改一個Object Storage Service),只需開啟具有現有資料庫版本更高的資料庫。這會調用 onupgradeneeded
事件處理函數。
要建立一個Object Storage Service,可以在資料庫物件上調用 createObjectStore
方法,如 清單 5 中所示。
清單 5. 建立Object Storage Service
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"}); };}
我們已經瞭解了Object Storage Service的工作原理。接下來,讓我們看看索引 如何引用包含資料的Object Storage Service。
回頁首
使用索引
除了使用鍵來檢索Object Storage Service中的記錄,還可使用代索引的欄位來檢索記錄。Object Storage Service可具有一個或多個索引。索引是一種特殊的Object Storage Service,它引用包含資料的Object Storage Service,在更改所引用的Object Storage Service時(也就是添加、修改或刪除記錄時)自動更新。
要建立一個索引,必須使用 清單 5 中所示的方法對資料庫進資料列版本設定。索引可以是惟一的,也可以是不惟一的。惟一索引要求索引中的所有值都是惟一的,比如使用一個電子郵件地址欄位。當某個值可以重複出現時,需要使用非惟一索引,比如城市、州或國家。清單 6 中的程式碼片段展示了如何在 employee 對象的 state 欄位上建立一個非惟一索引,在 ZIP code 欄位上建立一個非惟一索引,並在 email address 欄位上建立一個惟一索引:
清單 6. 建立索引
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 }) };}
接下來,您將使用事務對Object Storage Service執行一些操作。
回頁首
使用事務
您需要使用事務在Object Storage Service上執行所有讀取和寫入操作。類似於關聯式資料庫中的事務的工作原理,IndexedDB 事務提供了資料庫寫入操作的一個原子集合,這個集合要麼完全提交,要麼完全不提交。IndexedDB 事務還擁有資料庫操作的一個中止和提交工具。
表 1 列出並描述了 IndexedDB 提供的事務模式。
表 1. IndexedDB 事務模式
模式 |
描述 |
readonly |
提供對某個Object Storage Service的唯讀訪問,在查詢Object Storage Service時使用。 |
readwrite |
提供對某個Object Storage Service的讀取和寫入訪問權。 |
versionchange |
提供讀取和寫入訪問權來修改Object Storage Service定義,或者建立一個新的Object Storage Service。 |
預設的事務模式為 readonly
。您可在任何給定時刻開啟多個並發的 readonly
事務,但只能開啟一個 readwrite
事務。出於此原因,只有在資料更新時才考慮使用 readwrite
事務。單獨的(表示不能開啟任何其他並發事務)versionchange
事務操作一個資料庫或Object Storage Service。可以在 onupgradeneeded
事件處理函數中使用 versionchange
事務建立、修改或刪除一個Object Storage Service,或者將一個索引添加到Object Storage Service。
要在 readwrite
模式下為 employees
Object Storage Service建立一個事務,可以使用語句:var transaction = db.transaction("employees", "readwrite");
。
清單 7 中的 JavaScript 函數展示了如何使用一個事務,使用鍵來檢索 employees
Object Storage Service中的一條特定的員工記錄。
清單 7. 使用鍵擷取一個特定的記錄
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 函數展示了如何使用一個事務,以使用 emailIndex
索引而不是Object Storage Service鍵來檢索 employees
Object Storage Service中的特定員工記錄。
清單 8. 使用索引擷取特定的記錄
function fetchEmployeeByEmail() {try { var result = document.getElementById("result"); result.innerHTML = ""; if (localDatabase != null && localDatabase.db != null) { var range = IDBKeyRange.only("[email protected]"); 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){
清單 9 是使用 readwrite
事務建立新員工記錄的一個樣本。
清單 9. 建立一條新員工記錄
function addEmployee() { try { var result = document.getElementById("result"); result.innerHTML = ""; var transaction = localDatabase.db.transaction("employees", "readwrite"); var store = transaction.objectStore("employees"); if (localDatabase != null && localDatabase.db != null) { var request = store.add({ "id": "E5", "first_name" : "Jane", "last_name" : "Doh", "email" : "[email protected]", "street" : "123 Pennsylvania Avenue", "city" : "Washington D.C.", "state" : "DC", "zip_code" : "20500", }); request.onsuccess = function(e) { result.innerHTML = "Employee record was added successfully."; }; request.onerror = function(e) { console.log(e.value); result.innerHTML = "Employee record was not added."; }; } } catch(e){ console.log(e); }}
清單 10 是使用 readwrite
事務更新現有員工記錄的一個樣本。該樣本更改了記錄 ID 為 E3
的員工的電子郵件地址。
清單 10. 更新現有的員工記錄
function updateEmployee() {try { var result = document.getElementById("result"); result.innerHTML = ""; var transaction = localDatabase.db.transaction("employees", "readwrite"); var store = transaction.objectStore("employees"); var jsonStr; var employee; if (localDatabase != null && localDatabase.db != null) { store.get("E3").onsuccess = function(event) { employee = event.target.result; // save old value jsonStr = "OLD: " + JSON.stringify(employee); result.innerHTML = jsonStr; // update record employee.email = "[email protected]"; var request = store.put(employee); request.onsuccess = function(e) { console.log("Added Employee"); }; request.onerror = function(e) { console.log(e.value); }; // fetch record again store.get("E3").onsuccess = function(event) { employee = event.target.result; jsonStr = "
NEW: " + JSON.stringify(employee); result.innerHTML = result.innerHTML + jsonStr; }; // fetch employee again }; // fetch employee first time }}catch(e){ console.log(e);}}
清單 11 是一個建立或刪除Object Storage Service中的所有記錄的 readwrite
事務的樣本。像其他非同步事務一樣,clear
事務根據Object Storage Service是否已清除來調用 onsuccess
或 onerror
回調。
清單 11. 清除Object Storage Service事務
function clearAllEmployees() {try { var result = document.getElementById("result"); result.innerHTML = ""; if (localDatabase != null && localDatabase.db != null) { var store = localDatabase.db.transaction("employees", "readwrite").objectStore("employees"); store.clear().onsuccess = function(event) { result.innerHTML = "‘Employees‘ object store cleared"; }; }}catch(e){ console.log(e);}}
這些樣本示範了事務的一些常見用途。接下來您將看到 IndexedDB 中遊標的工作原理。
回頁首
使用遊標
類似於關聯式資料庫中遊標的工作方式,IndexedDB 中的遊標使您能夠迭代一個Object Storage Service中的記錄。您還可以使用Object Storage Service的索引來迭代記錄。IndexedDB 中的遊標是雙向的,所以您可向前和向後迭代記錄,還可以跳過非惟一索引中的重複記錄。openCursor
方法可以開啟一個遊標。它接受兩個可選的參數,其中包括範圍和方向。
清單 12 為 employees
Object Storage Service開啟一個遊標,并迭代所有員工記錄。
清單 12. 迭代所有員工記錄
function fetchAllEmployees() {try { var result = document.getElementById("result"); result.innerHTML = ""; if (localDatabase != null && localDatabase.db != null) { var store = localDatabase.db.transaction("employees").objectStore("employees"); var request = store.openCursor(); request.onsuccess = function(evt) { var cursor = evt.target.result; if (cursor) { var employee = cursor.value; var jsonStr = JSON.stringify(employee); result.innerHTML = result.innerHTML + "
" + jsonStr; cursor.continue(); } }; }}catch(e){ console.log(e);}}
接下來的這些樣本將遊標用於索引。表 2 列出並描述了 IndexedDB API 在為索引開啟遊標時提供的範圍類型或過濾器。
表 2. IndexedDB API 在為索引開啟遊標時提供的範圍類型或過濾器
範圍類型或過濾器 |
描述 |
IDBKeyRange.bound |
返回指定範圍內的所有記錄。這個範圍有一個下邊界和上邊界。它還有兩個可選的參數:lowerOpen 和upperOpen ,這兩個參數表明下邊界或上邊界上的記錄是否應包含在範圍內。 |
IDBKeyRange.lowerBound |
超過指定的邊界值範圍的所有記錄。此範圍有一個可選的參數 lowerOpen ,表明下邊界上的記錄是否應包含在範圍中。 |
IDBKeyRange.upperBound |
返回指定的邊界值之前的所有記錄。它也有一個可選的 upperOpen 參數。 |
IDBKeyRange.only |
僅返回與指定值匹配的記錄。 |
清單 13 是迭代特定國家的所有員工記錄的一個基本樣本。這個查詢最常見。它使您能夠檢索與特定條件匹配的所有記錄。這個樣本使用stateIndex
和 IDBKeyRange.only
範圍,返回與指定值(在本例中為 "New York"
)匹配的所有記錄。
清單 13. 迭代紐約市內的所有員工記錄。
function fetchNewYorkEmployees() {try { var result = document.getElementById("result"); result.innerHTML = ""; if (localDatabase != null && localDatabase.db != null) { var range = IDBKeyRange.only("New York"); var store = localDatabase.db.transaction("employees").objectStore("employees"); var index = store.index("stateIndex"); index.openCursor(range).onsuccess = function(evt) { var cursor = evt.target.result; if (cursor) { var employee = cursor.value; var jsonStr = JSON.stringify(employee); result.innerHTML = result.innerHTML + "
" + jsonStr; cursor.continue(); } }; }}catch(e){ console.log(e);}}
清單 14 是一個使用 IDBKeyRange.lowerBound
範圍的樣本。它迭代郵遞區號高於 92000 的所有員工。
清單 14. 使用
IDBKeyRange.lowerBound
function fetchEmployeeByZipCode1() {try { var result = document.getElementById("result"); result.innerHTML = ""; if (localDatabase != null && localDatabase.db != null) { var store = localDatabase.db.transaction("employees").objectStore("employees"); var index = store.index("zipIndex"); var range = IDBKeyRange.lowerBound("92000"); index.openCursor(range).onsuccess = function(evt) { var cursor = evt.target.result; if (cursor) { var employee = cursor.value; var jsonStr = JSON.stringify(employee); result.innerHTML = result.innerHTML + "
" + jsonStr; cursor.continue(); } }; }}catch(e){ console.log(e);}}
清單 15 是一個使用 IDBKeyRange.upperBound
範圍的樣本。它迭代郵遞區號基於 93000 的所有員工。
清單 15. 使用
IDBKeyRange.upperBound
function fetchEmployeeByZipCode2() {try { var result = document.getElementById("result"); result.innerHTML = ""; if (localDatabase != null && localDatabase.db != null) { var store = localDatabase.db.transaction("employees").objectStore("employees"); var index = store.index("zipIndex"); var range = IDBKeyRange.upperBound("93000"); index.openCursor(range).onsuccess = function(evt) { var cursor = evt.target.result; if (cursor) { var employee = cursor.value; var jsonStr = JSON.stringify(employee); result.innerHTML = result.innerHTML + "
" + jsonStr; cursor.continue(); } }; }}catch(e){ console.log(e);}}
清單 16 是一個使用 IDBKeyRange.bound
範圍的樣本。它檢索郵遞區號值在 92000 與 92999(含)之間的所有員工。
清單 16. 使用
IDBKeyRange.bound
function fetchEmployeeByZipCode3() {try { var result = document.getElementById("result"); result.innerHTML = ""; if (localDatabase != null && localDatabase.db != null) { var store = localDatabase.db.transaction("employees").objectStore("employees"); var index = store.index("zipIndex"); var range = IDBKeyRange.bound("92000", "92999", true, true); index.openCursor(range).onsuccess = function(evt) { var cursor = evt.target.result; if (cursor) { var employee = cursor.value; var jsonStr = JSON.stringify(employee); result.innerHTML = result.innerHTML + "
" + jsonStr; cursor.continue(); } }; }}catch(e){ console.log(e);}}
這些樣本表明,IndexedDB 中的遊標功能與關聯式資料庫中的遊標功能類似。使用 IndexedDB 遊標,可迭代一個Object Storage Service中的記錄和Object Storage Service的某個索引的記錄。IndexedDB 中的遊標是雙向的,這提供了額外的靈活性。
回頁首
結束語
IndexedDB API 非常強大,您可以使用它建立具有豐富本機存放區資料的資料密集型應用程式(尤其是離線的 HTML5 Web 應用程式)。您還可以使用 IndexedDB API 將資料緩衝到本地,使傳統的線上 Web 應用程式(尤其是移動 Web 應用程式)能夠更快地運行和響應,從而消除每次從 Web 服務器檢索資料的需求。例如,可以將挑選清單的資料緩衝在 IndexedDB 資料庫中。
本文展示了如何管理 IndexedDB 資料庫,包括建立資料庫,刪除資料庫,以及建立與資料庫的串連。本文還展示了 IndexedDB API 的許多更進階的功能,包括交易處理、索引和遊標。您可以使用這些展示的概念構建利用 IndexedDB API 的離線應用或移動 Web 應用程式。
[轉]使用 HTML5 IndexedDB API