使用 HTML5 IndexedDB API

HTML5 的一個重要特性是本地數據持久性,它使用戶可以在線和離線訪問 Web 應用程序。此外,本地數據持久性使移動應用程序更靈敏,使用的帶寬更少,並且可以在低帶寬場景中更高效地工做。HTML5 提供了一些本地數據持久性選項。第一個選項是 localstorage,它支持您使用一個簡單的鍵值對來存儲數據。IndexedDB(一個更增強大的選項)支持您本地存儲大量對象,並使用健壯的數據訪問機制檢索數據。數據庫

IndexedDB API 取代了 Web Storage API,後者在 HTML5 規範中已不推薦使用。(但一些領先的瀏覽器仍然支持 Web Storage,其中包括蘋果公司的 Safari 和 Opera Web 瀏覽器)與 Web Storage 相比,IndexedDB 具備多個優點,其中包括索引、事務處理和健壯的查詢功能。本文將經過一系列的示例來展現如何管理 IndexedDB 數據庫。(參見 下載 一節,獲取示例的完整源代碼。)json

重要概念瀏覽器

一個網站可能有一個或多個 IndexedDB 數據庫,每一個數據庫必須具備唯一的名稱。緩存

一個數據庫可包含一個或多個對象存儲。一個對象存儲(由一個名稱唯一標識)是一個記錄集合。每一個記錄有一個鍵 和一個值。該值是一個對象,可擁有一個或多個屬性。鍵可能基於某個鍵生成器,從一個鍵路徑衍生出來,或者是顯式設置。一個鍵生成器自動生成唯一的連續正整數。鍵路徑定義了鍵值的路徑。它能夠是單個 JavaScript 標識符或多個由句點分隔的標識符。服務器

規範中包含一個異步 API 和一個同步 API。同步 API 用於 Web 瀏覽器中。異步 API 使用請求和回調。併發

在如下示例中,輸出附加到一個具備 ID result 的 div 標記上。要更新 result 元素,可在每一個數據操做期間清除並設置 innerHTML 屬性。每一個示例 JavaScript 函數由 HTML 按鈕的一個 onclick 事件調用。dom

處理錯誤或異常和調試異步

全部異步請求都有一個 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。建立數據庫以後,數據庫(和它的對象存儲)只能經過一種稱爲 versionchange 的特殊類型的事務來更改。要在建立數據庫後更改它,必須打開具備更高版本的數據庫。此操做會觸發 upgradeneeded 事件。修改數據庫或對象存儲的代碼必須位於 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;

   };

}

建立、刪除和打開數據庫就是這麼簡單。如今是時候使用對象存儲了。

回頁首

使用對象存儲

對象存儲是一個數據記錄集合。要在現有數據庫中建立一個新對象存儲,則須要對現有數據庫進行版本控制。爲此,請打開要進行版本控制的數據庫。除了數據庫名稱以外,open 方法還接受版本號做爲第二個參數。若是但願建立數據庫的一個新版本(也就是說,要建立或修改一個對象存儲),只需打開具備現有數據庫版本更高的數據庫。這會調用 onupgradeneeded 事件處理函數。

要建立一個對象存儲,能夠在數據庫對象上調用 createObjectStore 方法,如 清單 5 中所示。

清單 5. 建立對象存儲

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"});

   };

}

咱們已經瞭解了對象存儲的工做原理。接下來,讓咱們看看索引 如何引用包含數據的對象存儲。

回頁首

使用索引

除了使用鍵來檢索對象存儲中的記錄,還可以使用代索引的字段來檢索記錄。對象存儲可具備一個或多個索引。索引是一種特殊的對象存儲,它引用包含數據的對象存儲,在更改所引用的對象存儲時(也就是添加、修改或刪除記錄時)自動更新。

要建立一個索引,必須使用 清單 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 })

   };

}

接下來,您將使用事務對對象存儲執行一些操做。

回頁首

使用事務

您須要使用事務在對象存儲上執行全部讀取和寫入操做。相似於關係數據庫中的事務的工做原理,IndexedDB 事務提供了數據庫寫入操做的一個原子集合,這個集合要麼徹底提交,要麼徹底不提交。IndexedDB 事務還擁有數據庫操做的一個停止和提交工具。

表 1 列出並描述了 IndexedDB 提供的事務模式。

表 1. IndexedDB 事務模式

模式 描述

readonly 提供對某個對象存儲的只讀訪問,在查詢對象存儲時使用。

readwrite 提供對某個對象存儲的讀取和寫入訪問權。

versionchange 提供讀取和寫入訪問權來修改對象存儲定義,或者建立一個新的對象存儲。

默認的事務模式爲 readonly。您可在任何給定時刻打開多個併發的 readonly 事務,但只能打開一個 readwrite 事務。出於此緣由,只有在數據更新時才考慮使用 readwrite 事務。單獨的(表示不能打開任何其餘併發事務)versionchange 事務操做一個數據庫或對象存儲。能夠在 onupgradeneeded 事件處理函數中使用 versionchange 事務建立、修改或刪除一個對象存儲,或者將一個索引添加到對象存儲。

要在 readwrite 模式下爲 employees 對象存儲建立一個事務,可使用語句:var transaction = db.transaction("employees", "readwrite");。

清單 7 中的 JavaScript 函數展現瞭如何使用一個事務,使用鍵來檢索 employees 對象存儲中的一條特定的員工記錄。

清單 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 索引而不是對象存儲鍵來檢索 employees 對象存儲中的特定員工記錄。

清單 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){

清單 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" : "jane.doh@somedomain.com",

            "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 = "john.adams@anotherdomain.com";


         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 是一個建立或刪除對象存儲中的全部記錄的 readwrite 事務的示例。像其餘異步事務同樣,clear 事務根據對象存儲是否已清除來調用 onsuccess 或 onerror 回調。

清單 11. 清除對象存儲事務

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 中的遊標使您可以迭代一個對象存儲中的記錄。您還可使用對象存儲的索引來迭代記錄。IndexedDB 中的遊標是雙向的,因此您可向前和向後迭代記錄,還能夠跳過非唯一索引中的重複記錄。openCursor 方法能夠打開一個遊標。它接受兩個可選的參數,其中包括範圍和方向。

清單 12 爲 employees 對象存儲打開一個遊標,並迭代全部員工記錄。

清單 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 遊標,可迭代一個對象存儲中的記錄和對象存儲的某個索引的記錄。IndexedDB 中的遊標是雙向的,這提供了額外的靈活性。

回頁首

結束語

IndexedDB API 很是強大,您可使用它建立具備豐富本地存儲數據的數據密集型應用程序(尤爲是離線的 HTML5 Web 應用程序)。您還可使用 IndexedDB API 將數據緩存到本地,使傳統的在線 Web 應用程序(尤爲是移動 Web 應用程序)可以更快地運行和響應,從而消除每次從 Web 服務器檢索數據的需求。例如,能夠將選擇列表的數據緩存在 IndexedDB 數據庫中。

本文展現瞭如何管理 IndexedDB 數據庫,包括建立數據庫,刪除數據庫,以及創建與數據庫的鏈接。本文還展現了 IndexedDB API 的許多更高級的功能,包括事務處理、索引和遊標。您可使用這些展現的概念構建利用 IndexedDB API 的離線應用或移動 Web 應用程序。

相關文章
相關標籤/搜索