天天看點

IndexedDB詳解簡介IndexedDB簡介IndexedDB的使用

簡介

IndexedDB是一種在浏覽器端存儲資料的方式。既然稱之為DB,是因為它豐富了用戶端的查詢方式,并且因為是本地存儲,可以有效的減少網絡對頁面資料的影響。

有了IndexedDB,浏覽器可以存儲更多的資料,進而豐富了浏覽器端的應用類型。

IndexedDB簡介

IndexedDB和傳統的關系型資料不同的是,它是一個key-value型的資料庫。

value可以是複雜的結構體對象,key可以是對象的某些屬性值也可以是其他的對象(包括二進制對象)。你可以使用對象中的任何屬性做為index,以加快查找。

IndexedDB是自帶transaction的,所有的資料庫操作都會綁定到特定的事務上,并且這些事務是自動送出了,IndexedDB并不支援手動送出事務。

IndexedDB API大部分都是異步的,在使用異步方法的時候,API不會立馬傳回要查詢的資料,而是傳回一個callback。

異步API的本質是向資料庫發送一個操作請求,當操作完成的時候,會收到一個DOM event,通過該event,我們會知道操作是否成功,并且獲得操作的結果。

IndexedDB是一種 NoSQL 資料庫,和關系型資料庫不同的是,IndexedDB是面向對象的,它存儲的是Javascript對象。

IndexedDB還有一個很重要的特點是其同源政策,每個源都會關聯到不同的資料庫集合,不同源是不允許通路其他源的資料庫,進而保證了IndexedDB的安全性。

IndexedDB的使用

這一節,我們将會以具體的例子來講解如何使用IndexedDB。

IndexedDB的浏覽器支援

不同的浏覽器對于IndexedDB有不同的實作,正常來說,我們可以使用window.indexedDB來擷取到浏覽器的indexedDB對象。但是對于某些浏覽器來說,還沒有使用标準的window.indexedDB,而是用帶字首的實作。

是以我們在使用過程中通常需要進行判斷和轉換:

// In the following line, you should include the prefixes of implementations you want to test.
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
// DON'T use "var indexedDB = ..." if you're not in a function.
// Moreover, you may need references to some window.IDB* objects:
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction || {READ_WRITE: "readwrite"}; // This line should only be needed if it is needed to support the object's constants for older browsers
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
// (Mozilla has never prefixed these objects, so we don't need window.mozIDB*)           

上面我們從window擷取了indexedDB,IDBTransaction和IDBKeyRange三個對象。

其中indexedDB表示的是資料庫的連接配接。IDBTransaction表示的是transaction,而IDBKeyRange則是用從資料庫的某個特定key range中取出資料。

但是,通常來說帶字首的實作一般都是不穩定的,是以我們通常不建議在正式環境中使用,是以如果不支援标準表達式的話,需要直接報錯:

if (!window.indexedDB) {
    console.log("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.");
}           

建立IndexedDB

要使用IndexedDB,我們首先需要open it:

// Let us open our database
var request = window.indexedDB.open("MyTestDatabase", 3);           

open方法傳回一個IDBOpenDBRequest對象,同時這是一個異步操作,open操作并不會立馬打開資料庫或者開啟事務,我們可以通過監聽request的事件來進行相應的處理。

open方法傳入兩個參數,第一個參數是資料庫的名字,第二個參數是資料庫的版本号。

當你建立一個新的資料庫或者更新一個現有的資料庫版本的時候,将會觸發一個onupgradeneeded事件,并在事件中傳入IDBVersionChangeEvent,我們可以通過event.target.result來擷取到IDBDatabase對象,然後通過這個對象來進行資料庫的版本更新操作。如下所示:

// This event is only implemented in recent browsers   
request.onupgradeneeded = function(event) { 
  // Save the IDBDatabase interface 
  var db = event.target.result;

  // Create an objectStore for this database
  var objectStore = db.createObjectStore("name", { keyPath: "myKey" });
};           

注意,這裡的版本号是一個整數。如果你傳入一個float,那麼将會對該float進行取整操作。

有了request,我們可以通過監聽onerror或者onsuccess事件來進行相應的處理。

var db;
var request = indexedDB.open("MyTestDatabase");
request.onerror = function(event) {
  console.log("Why didn't you allow my web app to use IndexedDB?!");
};
request.onsuccess = function(event) {
  db = event.target.result;
};           

拿到db對象之後,我們可以設定全局的異常處理:

db.onerror = function(event) {
  // Generic error handler for all errors targeted at this database's
  // requests!
  console.error("Database error: " + event.target.errorCode);
};           

IndexedDB中的table叫做object stores,和關系型資料庫中的table一樣,object stores中的每一個對象都和一個key相關聯,和key相關的有兩個概念 key path 和 key generator.

如果存儲的是javascript Object對象,那麼可以指定該對象中的某一個屬性作為key path,那麼這個屬性将會被作為key。

如果沒有指定key path,那麼存儲的Object可以是任何對象,甚至是基礎類型比如數字和String。

而key generator就是key的生成器。

假如我們想要存儲這樣的資料:

// This is what our customer data looks like.
const customerData = [
  { ssn: "444-44-4444", name: "Bill", age: 35, email: "[email protected]" },
  { ssn: "555-55-5555", name: "Donna", age: 32, email: "[email protected]" }
];           

看一下對應的資料庫操作是怎麼樣的:

const dbName = "the_name";

var request = indexedDB.open(dbName, 2);

request.onerror = function(event) {
  // Handle errors.
};
request.onupgradeneeded = function(event) {
  var db = event.target.result;

  // Create an objectStore to hold information about our customers. We're
  // going to use "ssn" as our key path because it's guaranteed to be
  // unique - or at least that's what I was told during the kickoff meeting.
  var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });

  // Create an index to search customers by name. We may have duplicates
  // so we can't use a unique index.
  objectStore.createIndex("name", "name", { unique: false });

  // Create an index to search customers by email. We want to ensure that
  // no two customers have the same email, so use a unique index.
  objectStore.createIndex("email", "email", { unique: true });

  // Use transaction oncomplete to make sure the objectStore creation is 
  // finished before adding data into it.
  objectStore.transaction.oncomplete = function(event) {
    // Store values in the newly created objectStore.
    var customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers");
    customerData.forEach(function(customer) {
      customerObjectStore.add(customer);
    });
  };
};           

我們需要在onupgradeneeded事件中處理所有的schema相關的操作。

首先使用db.createObjectStore建立了一個customers的ObjectStore,并且使用了對象的keypath作為key。

除了key之外,我們建立了兩個index,以提高查詢速度。

最後我們監聽transaction.oncomplete事件,并在裡面加入存儲object的操作。

上面的代碼中,我們使用了keyPath作為key。

下面是一個使用key Generator的例子:

var objStore = db.createObjectStore("names", { autoIncrement : true });           

indexdb中的CURD

indexedDB的所有操作都需要在事務中,我們看一個開啟事務的操作:

var transaction = db.transaction(["customers"], "readwrite");           

上面的例子中使用readwrite來操作customers ObjectStore。

transaction接收兩個參數,第一個參數是一個數組,數組中是這個trans中将會處理的ObjectStores,第二個參數是處理的模式。

有了transaction之後,我們可以監聽事務的complete和error操作,然後就可以進行add操作了:

// Do something when all the data is added to the database.
transaction.oncomplete = function(event) {
  console.log("All done!");
};

transaction.onerror = function(event) {
  // Don't forget to handle errors!
};

var objectStore = transaction.objectStore("customers");
customerData.forEach(function(customer) {
  var request = objectStore.add(customer);
  request.onsuccess = function(event) {
    // event.target.result === customer.ssn;
  };
});           

上面的例子中,我們使用了add方法,add的前提是資料庫中并不存在相同key的對象。除了add方法之外,我們還可以使用put方法,put方法主要用來進行更新操作。

再看一個删除的操作:

var request = db.transaction(["customers"], "readwrite")
                .objectStore("customers")
                .delete("444-44-4444");
request.onsuccess = function(event) {
  // It's gone!
};           

現在我們的資料庫已經有了資料,我們看下怎麼進行查詢:

var transaction = db.transaction(["customers"]);
var objectStore = transaction.objectStore("customers");
var request = objectStore.get("444-44-4444");
request.onerror = function(event) {
  // Handle errors!
};
request.onsuccess = function(event) {
  // Do something with the request.result!
  console.log("Name for SSN 444-44-4444 is " + request.result.name);           

這裡,我們直接使用了db.transaction,預設情況下是readonly模式的。

下面是一個更新的例子:

var objectStore = db.transaction(["customers"], "readwrite").objectStore("customers");
var request = objectStore.get("444-44-4444");
request.onerror = function(event) {
  // Handle errors!
};
request.onsuccess = function(event) {
  // Get the old value that we want to update
  var data = event.target.result;

  // update the value(s) in the object that you want to change
  data.age = 42;

  // Put this updated object back into the database.
  var requestUpdate = objectStore.put(data);
   requestUpdate.onerror = function(event) {
     // Do something with the error
   };
   requestUpdate.onsuccess = function(event) {
     // Success - the data is updated!
   };
};           

更新我們使用的是put方法。

使用遊标cursor

indexedDB支援遊标操作,我們可以使用cursor來周遊objectStore的資料:

var objectStore = db.transaction("customers").objectStore("customers");

objectStore.openCursor().onsuccess = function(event) {
  var cursor = event.target.result;
  if (cursor) {
    console.log("Name for SSN " + cursor.key + " is " + cursor.value.name);
    cursor.continue();
  }
  else {
    console.log("No more entries!");
  }
};           

openCursor可以接受多個參數,第一個參數可以接受key的查詢範圍,第二個參數用來指定周遊的方向。如果兩個參數都為空的話,預設是所有的資料的以升序的順序周遊。

如果想周遊下一個遊标,則可以調用cursor.continue。

我們看一下兩個參數的遊标使用:

// Only match "Donna"
var singleKeyRange = IDBKeyRange.only("Donna");

// Match anything past "Bill", including "Bill"
var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill");

// Match anything past "Bill", but don't include "Bill"
var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true);

// Match anything up to, but not including, "Donna"
var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true);

// Match anything between "Bill" and "Donna", but not including "Donna"
var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true);

// To use one of the key ranges, pass it in as the first argument of openCursor()/openKeyCursor()
index.openCursor(boundKeyRange, "prev").onsuccess = function(event) {
  var cursor = event.target.result;
  if (cursor) {
    // Do something with the matches.
    cursor.continue();
  }
};           

除了openCursor,我們還可以通過使用openKeyCursor來周遊KeyCursor:

// Using a normal cursor to grab whole customer record objects
index.openCursor().onsuccess = function(event) {
  var cursor = event.target.result;
  if (cursor) {
    // cursor.key is a name, like "Bill", and cursor.value is the whole object.
    console.log("Name: " + cursor.key + ", SSN: " + cursor.value.ssn + ", email: " + cursor.value.email);
    cursor.continue();
  }
};

// Using a key cursor to grab customer record object keys
index.openKeyCursor().onsuccess = function(event) {
  var cursor = event.target.result;
  if (cursor) {
    // cursor.key is a name, like "Bill", and cursor.value is the SSN.
    // No way to directly get the rest of the stored object.
    console.log("Name: " + cursor.key + ", SSN: " + cursor.primaryKey);
    cursor.continue();
  }
};           

除此之外,我們還可以直接通過index來進行查詢:

var index = objectStore.index("name");

index.get("Donna").onsuccess = function(event) {
  console.log("Donna's SSN is " + event.target.result.ssn);
};           

要使用index的前提就是需要在request.onupgradeneeded中建立index。

本文作者:flydean程式那些事

本文連結:

http://www.flydean.com/indexeddb-kickoff/

本文來源:flydean的部落格

歡迎關注我的公衆号:「程式那些事」最通俗的解讀,最深刻的幹貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!