indexedDB 基本使用

indexedDB 簡介: 


indexedDB 是一種使用瀏覽器存儲大量數據的方法。它創造的數據可以被查詢,並且可以離線使用。


indexedDB 有以下特點:


  1. indexedDB 是 WebSQL 數據庫的取代品

  2. indexedDB遵循同源協議(只能訪問同域中存儲的數據,而不能訪問其他域的)

  3. API包含異步API和同步API兩種:多數情況下使用異步API; 同步API必須同 WebWorkers 一起使用, 目前沒有瀏覽器支持同步API

  4. indexedDB 是事務模式的數據庫, 使用 key-value 鍵值對儲存數據

  5. indexedDB 不使用結構化查詢語言(SQL). 它通過索引(index)所產生的指針(cursor)來完成查詢操作


一、使用indexedDB的基本模式


  1. 打開數據庫並且開始一個事務。

  2. 創建一個 objecStore。

  3. 構建一個請求來執行一些數據庫操作,像增加或提取數據等。

  4. 通過監聽正確類型的 DOM 事件以等待操作完成。

  5. 在操作結果上進行一些操作(可以在 request 對象中找到)


二、創建、打開數據庫


indexedDB 存在於全局對象window上, 它最重要的一個方法就是open方法, 該方法接收兩個參數:


  • dbName // 數據庫名稱 [string]

  • version // 數據庫版本 [整型number]


var DB_NAME = 'indexedDB-test', VERSION = 1, db;

var request = indexedDB.open(DB_NAME, VERSION);

request.onsuccess = function(event) {

    db = event.target.result;

    // console.log(event.target === request); // true

    db.onsuccess = function(event) {

        console.log('數據庫操作成功!');

    };

    db.onerror = function(event) {

        console.error('數據庫操作發生錯誤!', event.target.errorCode);

    };

    console.log('打開數據庫成功!');

};

request.onerror = function(event) {

    console.error('創建數據庫出錯');

    console.error('error code:', event.target.errorCode);

};

request.onupgradeneeded = function(event) {

   // 更新對象存儲空間和索引 ....

};


若是本域下不存在名爲DB_NAME的數據庫,則上述代碼會創建一個名爲DB_NAME、版本號爲VERSION的數據庫; 觸發的事件依次爲: upgradeneeded、 success.


若是已存在名爲DB_NAME的數據庫, 則上述代碼會打開該數據庫; 只觸發success/error事件,不會觸發upgradeneeded事件. db是對該數據庫的引用.


三、創建對象存儲空間和索引


在關係型數據庫(如mysql)中,一個數據庫中會有多張表,每張表有各自的主鍵、索引等;


在key-value型數據庫(如indexedDB)中, 一個數據庫會有多個對象存儲空間,每個存儲空間有自己的主鍵、索引等;


創建對象存儲空間的操作一般放在創建數據庫成功回調裏:


request.onupgradeneeded = function(event) { // 更新對象存儲空間和索引 ....

    var database = event.target.result;

    var objectStore = database.createObjectStore("movies", { keyPath"id" });

    objectStore.createIndex('alt', 'alt', { uniquetrue });

    objectStore.createIndex('title', 'title', { uniquefalse });

};


onupgradeneeded 是我們唯一可以修改數據庫結構的地方。在這裏面,我們可以創建和刪除對象存儲空間以及構建和刪除索引。


在數據庫對象database上,有以下方法可供調用:


  1. createObjectStore(storeName, configObj) 創建一個對象存儲空間

    1. storeName // 對象存儲空間的名稱 [string]

    2. configObj // 該對象存儲空間的配置 [object] (其中的keyPath屬性值,標誌對象的該屬性值唯一)

  2. createIndex(indexName, objAttr, configObj) 創建一個索引

    1. indexName // 索引名稱 [string]

    2. objAttr // 對象的屬性名 [string]

    3. configObj // 該索引的配置對象 [object]


四、增加和刪除數據


對數據庫的操作(增刪查改等)都需要通過事務來完成,事務具有三種模式:


  • readonly 只讀(可以併發進行,優先使用)

  • readwrite 讀寫

  • versionchange 版本變更


向數據庫中增加數據


前面提到,增加數據需要通過事務,事務的使用方式如下:


var transaction = db.transaction(['movies'], 'readwrite');

transaction.oncomplete = function(event) {

    console.log('事務完成!');

};

transaction.onerror = function(event) {

    console.log('事務失敗!', event.target.errorCode);

};

transaction.onabort = function(event) {

    console.log('事務回滾!');

};



insert-data-web數據庫對象的transaction()方法接收兩個參數:


  • storeNames // 對象存儲空間,可以是對象存儲空間名稱的數組,也可以是單個對象存儲空間名稱,必傳 [array|string]

  • mode // 事務模式,上面提到的三種之一,可選,默認值是readonly [string]


這樣,我們得到一個事務對象transaction, 有三種事件可能會被觸發: complete, error, abort. 現在,我們通過事務向數據庫indexedDB-test的 對象存儲空間movies中插入數據:


var objectStore = transaction.objectStore('movies');  // 指定對象存儲空間

var data = [{

  "title""尋夢環遊記",

  "year""2017",

  "alt""https://movie.douban.com/subject/20495023/",

  "id""20495023"

}, {

  "title""你在哪",

  "year""2016",

  "alt""https://movie.douban.com/subject/26639033/",

  "id""26639033"

}, {

  "title""筆仙咒怨",

  "year""2017",

  "alt""https://movie.douban.com/subject/27054612/",

  "id""27054612"

}];

data.forEach(function(item, index){

    var request = objectStore.add(item);

    request.onsuccess = function(event) {

        console.log('插入成功!', index);

        console.log(event.target.result, item.id); // add()方法調用成功後result是被添加的值的鍵(id)

    };

});


通過事務對象transaction,在objectStore()方法中指定對象存儲空間,就得到了可以對該對象存儲空間進行操作的對象objectStore.


向數據庫中增加數據,add()方法增加的對象,若是數據庫中已存在相同的主鍵,或者唯一性索引的鍵值重複,則該條數據不會插入進去;


增加數據還有一個方法: put(), 使用方法和add()不同之處在於,數據庫中若存在相同主鍵或者唯一性索引重複,則會更新該條數據,否則插入新數據。


從數據庫中刪除數據


刪除數據使用delete方法,同上類似:


var request =

    db.transaction(['movies'], 'readwrite')

      .objectStore('movies')

      .delete('27054612');  // 通過鍵id來刪除

request.onsuccess = function(event) {

    console.log('刪除成功!');

    console.log(event.target.result);

};


從數據中獲取數據


獲取數據使用get方法,同上類似:


var request =

    db.transaction('movies')

       .objectStore('movies')

       .get('9999682');  // 通過鍵alt來獲取

request.onsuccess = function(event) {

    console.log('獲取成功!', event.target.result);

};


五、使用索引


在前面,我們創建了兩個索引alt和title, 配置對象裏面的unique屬性標誌該值是否唯一


現在我們想找到alt屬性值爲https://movie.douban.com/subject/26639033/的對象,就可以使用索引。


var alt = 'https://movie.douban.com/subject/26639033/';

var objectStore = db.transaction('movies').objectStore('movies');  // 打開對象存儲空間

var index = objectStore.index('alt');  // 使用索引'alt'

var request = index.get(alt);          // 創建一個查找數據的請求

request.onsuccess = function(event) {

    console.log('The result is:', event.target.result);

};

var noDataTest = index.get('testalt');  // 沒有該對象時的測試

noDataTest.onsuccess = function(event) {

    console.log('success! result:', event.target.result);

};

noDataTest.onerror = function(event) {

    console.log('error! event:', event);

};



使用唯一性索引,我們可以得到唯一的一條數據(或者undefined),那麼使用非唯一性索引呢?

我們向數據庫中插入一條數據,使title重複:


db.transaction('movies', 'readwrite').objectStore('movies')

.add({ alt'https://movie.douban.com/subject/27054612121/',

    title'尋夢環遊記',

    year'2017',

    id'123456789'

})

.onsuccess = function(event) { console.log('插入成功!'); };


使用索引title獲取title值爲尋夢環遊記的對象:


var indexName = 'title', title = '尋夢環遊記';

var objectStore = db.transaction('movies').objectStore('movies');

var index = objectStore.index(indexName);  // 使用索引'alt'

var request = index.get(title);          // 創建一個查找數據的請求

request.onsuccess = function(event) {

    console.log('The result is:', event.target.result);

};


我們得到的是鍵值最小的那個對象.


使用一次索引,我們只能得到一條數據; 如果我們需要得到所有title屬性值爲尋夢環遊記的對象,我們可以使用遊標.


六、使用遊標


得到一個可以操作遊標的請求對象有兩個方法:


  • openCursor(keyRange, direction)

  • openKeyCursor(keyRange, direction)


這兩個方法接收的參數一樣, 兩個參數都是可選的: 第一個參數是限制值得範圍,第二個參數是指定遊標方向


遊標的使用有以下幾處:


  • 在對象存儲空間上使用: var cursor = objectStore.openCursor()

  • 在索引對象上使用: var cursor = index.openCursor()


在對象存儲空間上使用遊標

使用遊標常見的一種模式是獲取對象存儲空間上的所有數據.


var list = [];

var objectStore = db.transaction('movies').objectStore('movies');

objectStore.openCursor().onsuccess = function(event) {

    var cursor = event.target.result;

    if (cursor) {

        console.log('cursor:', cursor);

        list.push(cursor.value);

        cursor.continue();

    } else {

        console.log('Get all data:', list);

    }

};


使用遊標時,需要在成功回調裏拿到result對象,判斷是否取完了數據:若數據已取完,result是undefined; 若未取完,則result是個IDBCursorWithValue對象,需調用continue()方法繼續取數據。 也可以根據自己需求, 對數據進行過濾。


在indexedDB2規範中,在對象存儲空間對象上納入了一個getAll()方法,可以獲取所有對象:


objectStore.getAll().onsuccess = function(event) {

    console.log('result:', event.target.result);

};


在索引上使用遊標


接着本文上述使用索引的例子,在索引title上使用openCursor()方法時,若不傳參數,則會遍歷所有數據,在成功回調中的到的result對象有以下屬性:


  • key 數據庫中這條對象的title屬性值

  • primaryKey 數據庫中這條對象的alt值

  • value 數據庫中這條對象

  • direction openCursor()方法傳入的第二個對象,默認值爲next


source IDBIndex對象 舉例如下:


var index = db

.transaction('movies')

.objectStore('movies').index('title');

index.openCursor().onsuccess = function(event) {

  var cursor = event.target.result;

  if (cursor) {

      console.log('cursor:', cursor);

      cursor.continue();

  }

};


在索引title上使用openKeyCursor()方法,若不傳參數,同樣也會遍歷所有數據,result對象屬性如下:


  • key 數據庫中這條對象的title屬性值

  • primaryKey 數據庫中這條對象的alt值

  • direction openCursor()方法傳入的第二個對象,默認值爲next

  • source altBIndex對象


和openCursor()方法相比,得到的數據少一個value屬性,是沒有辦法得到存儲對象的其餘部分


前面說到,我們要根據索引title獲取所有title屬性值爲尋夢環遊記的對象,要使用遊標,而又不想遍歷所有數據,這時就要用到openCursor()的第一個參數: keyRange


keyRange是限定遊標遍歷的數據範圍,通過IDBKeyRange的一些方法設置該值:


var singleKeyRange = IDBKeyRange.only("尋夢環遊記"), list = [];

var index = db

.transaction('movies')

.objectStore('movies').index('title');

index.openCursor(singleKeyRange).onsuccess = function(event) {

var cursor = event.target.result;

if (cursor) {

console.log('cursor.value:', cursor.value);

list.push(cursor.value);

cursor.continue();

} else {

    console.log('list:', list);

}

};


IDBKeyRange其他一些方法:


// 匹配所有在 "Bill" 前面的, 包括 "Bill"

var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill");

 

// 匹配所有在 “Bill” 前面的, 但是不需要包括 "Bill"

var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true);

 

// 匹配所有在'Donna'後面的, 但是不包括"Donna"

var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true);

 

// 匹配所有在"Bill" 和 "Donna" 之間的, 但是不包括 "Donna"

var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true);


更多請參考 MDN|IDBKeyRange


遊標默認遍歷方向是按主鍵從小到大,有時候我們倒序遍歷,此時可以給openCursor()方法傳遞第二個參數: direction: next|nextunique|prev|prevunique


var singleKeyRange = IDBKeyRange.only("尋夢環遊記"), list = [];

var index = db

.transaction('movies')

.objectStore('movies').index('title');

index.openCursor(singleKeyRange, 'prev').onsuccess = function(event) {

var cursor = event.target.result;

if (cursor) {

console.log('cursor.value:', cursor.value);

list.push(cursor.value);

cursor.continue();

} else {

    console.log('list:', list);

}

};


傳了prev的結果是按倒序遍歷的.


因爲 “name” 索引不是唯一的,那就有可能存在具有相同 name 的多條記錄。 要注意的是這種情況不可能發生在對象存儲空間上,因爲鍵必須永遠是唯一的。 如果你想要在遊標在索引迭代過程中過濾出重複的,你可以傳遞 nextunique(或prevunique, 如果你正在向後尋找)作爲方向參數。 當 nextunique 或是 prevunique 被使用時,被返回的那個總是鍵最小的記錄。


var singleKeyRange = IDBKeyRange.only("尋夢環遊記"), list = [];

var index = db

.transaction('movies')

.objectStore('movies').index('title');

index.openCursor(singleKeyRange, 'prevunique').onsuccess = function(event) {

var cursor = event.target.result;

if (cursor) {

console.log('cursor.value:', cursor.value);

list.push(cursor.value);

cursor.continue();

} else {

    console.log('list:', list);

}

};



七、關閉和刪除數據庫


關閉數據庫只需要在數據庫對象db上調用close()方法即可


db.close();


關閉數據庫後,db對象仍然保存着該數據庫的相關信息,只是無法再開啓事務(調用開啓事務方法會報錯,提示數據庫連接已斷開):



刪除數據庫則需要使用indexedDB.deleteDatabase(dbName)方法


window.indexedDB.deleteDatabase(dbName);


八、indexedDB的侷限性


以下情況不適合使用IndexedDB


  • 全球多種語言混合存儲。國際化支持不好。需要自己處理。

  • 和服務器端數據庫同步。你得自己寫同步代碼。

  • 全文搜索。


注意,在以下情況下,數據庫可能被清除:


  • 用戶請求清除數據。

  • 瀏覽器處於隱私模式。最後退出瀏覽器的時候,數據會被清除。

  • 硬盤等存儲設備的容量到限。

  • 不正確的

  • 不完整的改變.


總結


  1. 使用indexedDB.open(dbName, version)打開一個數據庫連接

  2. 使用indexedDB.deleteDatabase(dbName)刪除一個數據庫

  3. 在數據庫對象db上使用createObjectStore(storeName, config)創建對象存儲空間

  4. 在對象存儲空間objectStore上使用createIndex(indexName, keyName, config)創建索引

  5. 對數據庫的操作都需要通過事務完成: var transction = db.transaction([storeName], mode)

  6. 數據庫的增刪改查均通過objectStore對象完成,var objectStore = transaction.objectStore(storeName)

  7. 對數據庫數據操作有: add()、get()、delete()、put等方法

  8. 查找數據可以使用索引: objectStore.index(indexName)

  9. 遍歷和過濾數據可以使用遊標: openCursor(keyRange, direction)


參考


  • IndexedDB的基本概念-MDN

  • 使用 IndexedDB-MDN

  • IndexedDB API接口-MDN

  • Indexed Database API 2.0 – w3c


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章