IndexedDB:瀏覽器端數(shù)據(jù)庫

2021-09-15 15:18 更新

概述

隨著瀏覽器的處理能力不斷增強(qiáng),越來越多的網(wǎng)站開始考慮,將大量數(shù)據(jù)儲存在客戶端,這樣可以減少用戶等待從服務(wù)器獲取數(shù)據(jù)的時間。

現(xiàn)有的瀏覽器端數(shù)據(jù)儲存方案,都不適合儲存大量數(shù)據(jù):cookie不超過4KB,且每次請求都會發(fā)送回服務(wù)器端;Window.name屬性缺乏安全性,且沒有統(tǒng)一的標(biāo)準(zhǔn);localStorage在2.5MB到10MB之間(各家瀏覽器不同)。所以,需要一種新的解決方案,這就是IndexedDB誕生的背景。

通俗地說,IndexedDB就是瀏覽器端數(shù)據(jù)庫,可以被網(wǎng)頁腳本程序創(chuàng)建和操作。它允許儲存大量數(shù)據(jù),提供查找接口,還能建立索引。這些都是localStorage所不具備的。就數(shù)據(jù)庫類型而言,IndexedDB不屬于關(guān)系型數(shù)據(jù)庫(不支持SQL查詢語句),更接近NoSQL數(shù)據(jù)庫。

IndexedDB具有以下特點。

(1)鍵值對儲存。 IndexedDB內(nèi)部采用對象倉庫(object store)存放數(shù)據(jù)。所有類型的數(shù)據(jù)都可以直接存入,包括JavaScript對象。在對象倉庫中,數(shù)據(jù)以“鍵值對”的形式保存,每一個數(shù)據(jù)都有對應(yīng)的鍵名,鍵名是獨一無二的,不能有重復(fù),否則會拋出一個錯誤。

(2)異步。 IndexedDB操作時不會鎖死瀏覽器,用戶依然可以進(jìn)行其他操作,這與localStorage形成對比,后者的操作是同步的。異步設(shè)計是為了防止大量數(shù)據(jù)的讀寫,拖慢網(wǎng)頁的表現(xiàn)。

(3)支持事務(wù)。 IndexedDB支持事務(wù)(transaction),這意味著一系列操作步驟之中,只要有一步失敗,整個事務(wù)就都取消,數(shù)據(jù)庫回到事務(wù)發(fā)生之前的狀態(tài),不存在只改寫一部分?jǐn)?shù)據(jù)的情況。

(4)同域限制 IndexedDB也受到同域限制,每一個數(shù)據(jù)庫對應(yīng)創(chuàng)建該數(shù)據(jù)庫的域名。來自不同域名的網(wǎng)頁,只能訪問自身域名下的數(shù)據(jù)庫,而不能訪問其他域名下的數(shù)據(jù)庫。

(5)儲存空間大 IndexedDB的儲存空間比localStorage大得多,一般來說不少于250MB。IE的儲存上限是250MB,Chrome和Opera是剩余空間的某個百分比,F(xiàn)irefox則沒有上限。

(6)支持二進(jìn)制儲存。 IndexedDB不僅可以儲存字符串,還可以儲存二進(jìn)制數(shù)據(jù)。

目前,Chrome 27+、Firefox 21+、Opera 15+和IE 10+支持這個API,但是Safari完全不支持。

下面的代碼用來檢查瀏覽器是否支持這個API。

if("indexedDB" in window) {
    // 支持
} else {
    // 不支持
}

indexedDB.open方法

瀏覽器原生提供indexedDB對象,作為開發(fā)者的操作接口。indexedDB.open方法用于打開數(shù)據(jù)庫。

var openRequest = indexedDB.open("test",1);

open方法的第一個參數(shù)是數(shù)據(jù)庫名稱,格式為字符串,不可省略;第二個參數(shù)是數(shù)據(jù)庫版本,是一個大于0的正整數(shù)(0將報錯)。上面代碼表示,打開一個名為test、版本為1的數(shù)據(jù)庫。如果該數(shù)據(jù)庫不存在,則會新建該數(shù)據(jù)庫。如果省略第二個參數(shù),則會自動創(chuàng)建版本為1的該數(shù)據(jù)庫。

打開數(shù)據(jù)庫的結(jié)果是,有可能觸發(fā)4種事件。

  • success:打開成功。
  • error:打開失敗。
  • upgradeneeded:第一次打開該數(shù)據(jù)庫,或者數(shù)據(jù)庫版本發(fā)生變化。
  • blocked:上一次的數(shù)據(jù)庫連接還未關(guān)閉。

第一次打開數(shù)據(jù)庫時,會先觸發(fā)upgradeneeded事件,然后觸發(fā)success事件。

根據(jù)不同的需要,對上面4種事件設(shè)立回調(diào)函數(shù)。

var openRequest = indexedDB.open("test",1);
var db;

openRequest.onupgradeneeded = function(e) {
    console.log("Upgrading...");
}

openRequest.onsuccess = function(e) {
    console.log("Success!");
    db = e.target.result;
}

openRequest.onerror = function(e) {
    console.log("Error");
    console.dir(e);
}

上面代碼有兩個地方需要注意。首先,open方法返回的是一個對象(IDBOpenDBRequest),回調(diào)函數(shù)定義在這個對象上面。其次,回調(diào)函數(shù)接受一個事件對象event作為參數(shù),它的target.result屬性就指向打開的IndexedDB數(shù)據(jù)庫。

indexedDB實例對象的方法

獲得數(shù)據(jù)庫實例以后,就可以用實例對象的方法操作數(shù)據(jù)庫。

createObjectStore方法

createObjectStore方法用于創(chuàng)建存放數(shù)據(jù)的“對象倉庫”(object store),類似于傳統(tǒng)關(guān)系型數(shù)據(jù)庫的表格。

db.createObjectStore("firstOS");

上面代碼創(chuàng)建了一個名為firstOS的對象倉庫,如果該對象倉庫已經(jīng)存在,就會拋出一個錯誤。為了避免出錯,需要用到下文的objectStoreNames屬性,檢查已有哪些對象倉庫。

createObjectStore方法還可以接受第二個對象參數(shù),用來設(shè)置“對象倉庫”的屬性。

db.createObjectStore("test", { keyPath: "email" }); 
db.createObjectStore("test2", { autoIncrement: true });

上面代碼中的keyPath屬性表示,所存入對象的email屬性用作每條記錄的鍵名(由于鍵名不能重復(fù),所以存入之前必須保證數(shù)據(jù)的email屬性值都是不一樣的),默認(rèn)值為null;autoIncrement屬性表示,是否使用自動遞增的整數(shù)作為鍵名(第一個數(shù)據(jù)為1,第二個數(shù)據(jù)為2,以此類推),默認(rèn)為false。一般來說,keyPath和autoIncrement屬性只要使用一個就夠了,如果兩個同時使用,表示鍵名為遞增的整數(shù),且對象不得缺少指定屬性。

objectStoreNames屬性

objectStoreNames屬性返回一個DOMStringList對象,里面包含了當(dāng)前數(shù)據(jù)庫所有“對象倉庫”的名稱??梢允褂肈OMStringList對象的contains方法,檢查數(shù)據(jù)庫是否包含某個“對象倉庫”。

if(!db.objectStoreNames.contains("firstOS")) {
     db.createObjectStore("firstOS");
}

上面代碼先判斷某個“對象倉庫”是否存在,如果不存在就創(chuàng)建該對象倉庫。

transaction方法

transaction方法用于創(chuàng)建一個數(shù)據(jù)庫事務(wù)。向數(shù)據(jù)庫添加數(shù)據(jù)之前,必須先創(chuàng)建數(shù)據(jù)庫事務(wù)。

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

transaction方法接受兩個參數(shù):第一個參數(shù)是一個數(shù)組,里面是所涉及的對象倉庫,通常是只有一個;第二個參數(shù)是一個表示操作類型的字符串。目前,操作類型只有兩種:readonly(只讀)和readwrite(讀寫)。添加數(shù)據(jù)使用readwrite,讀取數(shù)據(jù)使用readonly。

transaction方法返回一個事務(wù)對象,該對象的objectStore方法用于獲取指定的對象倉庫。

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

var store = t.objectStore("firstOS");

transaction方法有三個事件,可以用來定義回調(diào)函數(shù)。

  • abort:事務(wù)中斷。
  • complete:事務(wù)完成。
  • error:事務(wù)出錯。
var transaction = db.transaction(["note"], "readonly");  

transaction.oncomplete = function(event) {
      // some code
};

事務(wù)對象有以下方法,用于操作數(shù)據(jù)。

(1)添加數(shù)據(jù):add方法

獲取對象倉庫以后,就可以用add方法往里面添加數(shù)據(jù)了。

var store = t.objectStore("firstOS");

var o = {p: 123};

var request = store.add(o,1);

add方法的第一個參數(shù)是所要添加的數(shù)據(jù),第二個參數(shù)是這條數(shù)據(jù)對應(yīng)的鍵名(key),上面代碼將對象o的鍵名設(shè)為1。如果在創(chuàng)建數(shù)據(jù)倉庫時,對鍵名做了設(shè)置,這里也可以不指定鍵名。

add方法是異步的,有自己的success和error事件,可以對這兩個事件指定回調(diào)函數(shù)。

var request = store.add(o,1);

request.onerror = function(e) {
     console.log("Error",e.target.error.name);
    // error handler
}

request.onsuccess = function(e) {
    console.log("數(shù)據(jù)添加成功!");
}

(2)讀取數(shù)據(jù):get方法

讀取數(shù)據(jù)使用get方法,它的參數(shù)是數(shù)據(jù)的鍵名。

var t = db.transaction(["test"], "readonly");
var store = t.objectStore("test");

var ob = store.get(x);

get方法也是異步的,會觸發(fā)自己的success和error事件,可以對它們指定回調(diào)函數(shù)。

var ob = store.get(x);

ob.onsuccess = function(e) {
    // ...
}

從創(chuàng)建事務(wù)到讀取數(shù)據(jù),所有操作方法也可以寫成下面這樣鏈?zhǔn)叫问健?/p>

db.transaction(["test"], "readonly")
  .objectStore("test")
  .get(X)
  .onsuccess = function(e){}

(3)更新記錄:put方法

put方法的用法與add方法相近。

var o = { p:456 };
var request = store.put(o, 1);

(4)刪除記錄:delete方法

刪除記錄使用delete方法。

var t = db.transaction(["people"], "readwrite");
var request = t.objectStore("people").delete(thisId);

delete方法的參數(shù)是數(shù)據(jù)的鍵名。另外,delete也是一個異步操作,可以為它指定回調(diào)函數(shù)。

(5)遍歷數(shù)據(jù):openCursor方法

如果想要遍歷數(shù)據(jù),就要openCursor方法,它在當(dāng)前對象倉庫里面建立一個讀取光標(biāo)(cursor)。

var t = db.transaction(["test"], "readonly");
var store = t.objectStore("test");

var cursor = store.openCursor();

openCursor方法也是異步的,有自己的success和error事件,可以對它們指定回調(diào)函數(shù)。

cursor.onsuccess = function(e) {
    var res = e.target.result;
    if(res) {
        console.log("Key", res.key);
        console.dir("Data", res.value);
        res.continue();
    }
}

回調(diào)函數(shù)接受一個事件對象作為參數(shù),該對象的target.result屬性指向當(dāng)前數(shù)據(jù)對象。當(dāng)前數(shù)據(jù)對象的key和value分別返回鍵名和鍵值(即實際存入的數(shù)據(jù))。continue方法將光標(biāo)移到下一個數(shù)據(jù)對象,如果當(dāng)前數(shù)據(jù)對象已經(jīng)是最后一個數(shù)據(jù)了,則光標(biāo)指向null。

openCursor方法還可以接受第二個參數(shù),表示遍歷方向,默認(rèn)值為next,其他可能的值為prev、nextunique和prevunique。后兩個值表示如果遇到重復(fù)值,會自動跳過。

createIndex方法

createIndex方法用于創(chuàng)建索引。

假定對象倉庫中的數(shù)據(jù)對象都是下面person類型的。

var person = {
    name:name,
    email:email,
    created:new Date()
}

可以指定這個數(shù)據(jù)對象的某個屬性來建立索引。

var store = db.createObjectStore("people", { autoIncrement:true });

store.createIndex("name","name", {unique:false});
store.createIndex("email","email", {unique:true});

createIndex方法接受三個參數(shù),第一個是索引名稱,第二個是建立索引的屬性名,第三個是參數(shù)對象,用來設(shè)置索引特性。unique表示索引所在的屬性是否有唯一值,上面代碼表示name屬性不是唯一值,email屬性是唯一值。

index方法

有了索引以后,就可以針對索引所在的屬性讀取數(shù)據(jù)。index方法用于從對象倉庫返回指定的索引。

var t = db.transaction(["people"],"readonly");
var store = t.objectStore("people");
var index = store.index("name");

var request = index.get(name);

上面代碼打開對象倉庫以后,先用index方法指定索引在name屬性上面,然后用get方法讀取某個name屬性所在的數(shù)據(jù)。如果沒有指定索引的那一行代碼,get方法只能按照鍵名讀取數(shù)據(jù),而不能按照name屬性讀取數(shù)據(jù)。需要注意的是,這時get方法有可能取回多個數(shù)據(jù)對象,因為name屬性沒有唯一值。

另外,get是異步方法,讀取成功以后,只能在success事件的回調(diào)函數(shù)中處理數(shù)據(jù)。

IDBKeyRange對象

索引的有用之處,還在于可以指定讀取數(shù)據(jù)的范圍。這需要用到瀏覽器原生的IDBKeyRange對象。

IDBKeyRange對象的作用是生成一個表示范圍的Range對象。生成方法有四種:

  • lowerBound方法:指定范圍的下限。
  • upperBound方法:指定范圍的上限。
  • bound方法:指定范圍的上下限。
  • only方法:指定范圍中只有一個值。

下面是一些代碼實例:

// All keys ≤ x 
var r1 = IDBKeyRange.upperBound(x);

// All keys < x 
var r2 = IDBKeyRange.upperBound(x, true);

// All keys ≥ y  
var r3 = IDBKeyRange.lowerBound(y);

// All keys > y 
var r4 = IDBKeyRange.lowerBound(y, true);

// All keys ≥ x && ≤ y 
var r5 = IDBKeyRange.bound(x, y);

// All keys > x &&< y    
var r6 = IDBKeyRange.bound(x, y, true, true);

// All keys > x && ≤ y    
var r7 = IDBKeyRange.bound(x, y, true, false);

// All keys ≥ x &&< y 
var r8 = IDBKeyRange.bound(x, y, false, true);

// The key = z 
var r9 = IDBKeyRange.only(z);

前三個方法(lowerBound、upperBound和bound)默認(rèn)包括端點值,可以傳入一個布爾值,修改這個屬性。

生成Range對象以后,將它作為參數(shù)輸入openCursor方法,就可以在所設(shè)定的范圍內(nèi)讀取數(shù)據(jù)。

var t = db.transaction(["people"],"readonly");
var store = t.objectStore("people");
var index = store.index("name");

var range = IDBKeyRange.bound('B', 'D');

index.openCursor(range).onsuccess = function(e) {
        var cursor = e.target.result;
        if(cursor) {
            console.log(cursor.key + ":");
            for(var field in cursor.value) {
                console.log(cursor.value[field]);
            }
            cursor.continue();
        }
}

參考鏈接

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號