簡介
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的部落格
歡迎關注我的公衆号:「程式那些事」最通俗的解讀,最深刻的幹貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!