IndexedDB 打造靠譜 Web 離線數據庫

在知乎和我在尋常事情中,經常會看到一個題目:

前端如今還火嗎?

這個我只想說:

袖手旁觀的人永遠沒法邃曉起火的緣由,只要置身風暴,才找到風眼之地點 ——『秦時明月』

你 TM 看都不看前端如今的生長,怎樣去評判前端火不火,我該不該嘗試一下其他方面的內容呢?本人為啥為這麼熱衷於新的手藝呢?主要緣由在於,恐怕會被某一項顛覆性的內容鐫汰掉,從前沿範疇落伍下來。說句人話就是:窮,所以只能學了...。所以本文會從新理會一下 IndexedDB 在前端內里的運用的生長。

indexedDB 現在在前端逐步取得提高和運用。它正朝着前端離線數據庫手藝的步調行進。之前一最先是 manifest、localStorage、cookie 再到 webSQL,如今 indexedDB 逐步被各大瀏覽器承認。我們也可以針對它來舉行手藝上立異的開闢。比方,如今小視頻非常盛行,那末我們可以在用戶寓目時,經由過程 cacheStorage 緩存,然後應用 WebRTC 手藝完成 P2P 分發的掌握,不過須要注重,肯定要合理應用大小,不然效果真的很嚴重。

indexedDB 的團體架構,是由一系列零丁的觀點串連而成,悉數觀點以下列表。一眼看去會發明沒有任何邏輯,不過,這裏我隨手畫了一幅邏輯圖,中心會依據 函數 的挪用而互相串連起來。

  • IDBRequest
  • IDBFactory
  • IDBDatabase
  • IDBObjectStore
  • IDBIndex
  • IDBKeyRange
  • IDBCursor
  • IDBTransaction

團體邏輯圖以下:

《IndexedDB 打造靠譜 Web 離線數據庫》

TL;DR

下文主要引見了 indexedDB 的基礎觀點,以及在現實運用中的實操代碼。

  • indexedDB 基礎觀點。在 indexedDB 內里會依據索引 index 來舉行團體數據組織的分別。
  • indexedDB 數據庫的更新是一個非常蛋疼的事變,因為,Web 的靈活性,你既須要做好向上版本的更新,也須要完美向下版本的容錯性。
  • indexedDB 高效索引機制,在內部,indexedDB 已供應了 indexcursor等高效的索引機制,引薦不要直接將一切數據都取回來,再舉行挑選,而是直接應用 cursor 舉行。
  • 末了引薦幾個經常運用庫

離線存儲

IndexedDB 可以存儲非常多的數據,比方 Object,files,blobs 等,內里的存儲組織是依據 Database 來舉行存儲的。每一個 DB 內里可以有差別的 object stores。細緻組織以下圖:

《IndexedDB 打造靠譜 Web 離線數據庫》

而且,我們可以給 key 設定相干特定的值,然後在索引的時候,可以直接經由過程 key 取得細緻的內容。運用 IndexDB 須要注重,其遵照的是同域準繩。

indexDB 基礎觀點

在 indexDB 中,有幾個基礎的操縱對象:

  • Database: 經由過程 open 要領直接翻開,可以取得一個實例的 DB。每一個頁面可以豎立多個 DB,不過平常都是一個。
idb.open(name, version, upgradeCallback)
  • Object store: 這個就是 DB 內里細緻存儲的對象。這個可以對應於 SQL 內里的 table 內容。其存儲的組織為:

《IndexedDB 打造靠譜 Web 離線數據庫》

  • index: 有點相似於外鏈,它本身是一種 Object store,重假如用來在本體的 store 中,索引別的 object store 內里的數據。須要辨別的是,key 和 index 是不一樣的。可以參考: index DEMOmdn index。以下圖示意:

《IndexedDB 打造靠譜 Web 離線數據庫》

以下 code 為:

// 豎立 index
var myIndex = objectStore.index('lName'); 
  • transaction: 事宜實在就是一系列 CRUD 的鳩合內容。假如个中一個環節失利了,那末全部事宜的處置懲罰都邑被作廢。比方:
var trans1 = db.transaction("foo", "readwrite");
var trans2 = db.transaction("foo", "readwrite");
var objectStore2 = trans2.objectStore("foo")
var objectStore1 = trans1.objectStore("foo")
objectStore2.put("2", "key");
objectStore1.put("1", "key");
  • cursor: 重假如用來遍歷 DB 內里的數據內容。重假如經由過程 openCursor 來舉行掌握。
function displayData() {
  var transaction = db.transaction(['rushAlbumList'], "readonly");
  var objectStore = transaction.objectStore('rushAlbumList');

  objectStore.openCursor().onsuccess = function(event) {
    var cursor = event.target.result;
    if(cursor) {
      var listItem = document.createElement('li');
      listItem.innerHTML = cursor.value.albumTitle + ', ' + cursor.value.year;
      list.appendChild(listItem);  

      cursor.continue();
    } else {
      console.log('Entries all displayed.');
    }
  };
}

怎樣運用 IndexDB

上面說了幾個基礎的觀點。那接下來我們實踐一下 IndexDB。現實上入門 IndexDB 就是做幾個基礎的內容

  • 翻開數據庫表
  • 設置指定的 primary Key
  • 定義好索引的 index

前期搭建一個 IndexedDB 很簡樸的代碼以下:

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

request.onerror = function(event) {
  // 毛病處置懲罰遞次在這裏。
};
request.onupgradeneeded = function(event) {
  var db = event.target.result;
  // 設置 id 為 primaryKey 參數
  var objectStore = db.createObjectStore("customers", { keyPath: "id",{autoIncrement:true} });
  
  // 設置指定索引,並確保唯一性
  objectStore.createIndex("name", "name", { unique: false });
  objectStore.createIndex("email", "email", { unique: true });

};

上面主要做了 3 件事:

  • 翻開數據庫表
  • 新建 Store,並設置 primary Key
  • 設置 index

翻開數據庫表主要就是版本號和名字,沒有太多講的,我們直接從豎立 store 最先吧。

豎立 Object Store

運用的要領就是 IDBDatabase 上的 createObjectStore 要領。

var objectStore = db.createObjectStore("customers", { keyPath: "id",{autoIncrement:true} });

基礎函數組織為:

IDBObjectStore createObjectStore(DOMString name,
                                               optional IDBObjectStoreParameters options)
                                               
dictionary IDBObjectStoreParameters {
  (DOMString or sequence<DOMString>)? keyPath = null;
  boolean autoIncrement = false;
};
  • keyPath: 用來設置主鍵的 key,細緻辨別可以參考下面的 keyPath 和 generator 的辨別。
  • autoIncrement: 是不是運用自增 key 的特徵。

豎立的 key 重假如為了保證,在數據插進去時唯一性的標識。

不過,每每一個主鍵(key),是沒方法很好的完成索引,在細緻實踐時,就還須要輔鍵 (aid-key) 來完成輔佐索引事情,這個在 IndexDB 就映照為 index

設置索引 index

在完成 PK(Primary key) 豎立終了后,為了更好的搜刮機能我們還須要分外豎立 index。這裏可以直接運用:

objectStore.createIndex('indexName', 'property', options);
  • indexName: 設置當前 index 的名字
  • property: 從存儲數據中,指明 index 所指的屬性。

个中,options 有三個選項:

  • unique: 當前 key 是不是能反覆 (最經常運用)
  • multiEntry: 設置當前的 property 為數組時,會給數組內里每一個元素都設置一個 index 值。
# 豎立一個名字叫 titleIndex 的 index,而且存儲的 index 不能反覆
DB.createIndex('titleIndex', 'title', {unique: false});

細緻可以參考:MDN createIndex PropgoogleDeveloper Index

增刪數據

在 IndexedDB 內里舉行數據的增刪,都須要在 transaction 中完成。而這個增刪數據,人人可以明白為一次 request,相當於在一個 transaction 內里治理一切當前邏輯操縱的 request。所以,在正式最先舉行數據操縱之前,還須要給人人簡樸引見一些假如豎立一個事宜。

事宜的豎立

transaction API,以下 [代碼1]。在豎立時,你須要手動指定當前 transaction 是那種範例的操縱,基礎的內容有:

  • “readonly”:只讀
  • “readwrite”:讀寫
  • “versionchange”:這個不能手動指定,會在 upgradeneeded 回調事宜內里自動豎立。它可以用來修正現有 object store 的組織數據,比方 index 等。

你可以經由過程在數據庫翻開以後,經由過程 IDBDataBase 上的 transaction 要領豎立,如 [代碼2]。

[代碼1]
  [NewObject] IDBTransaction transaction((DOMString or sequence<DOMString>) storeNames,
                                         optional IDBTransactionMode mode = "readonly");
                                         
[代碼2]
var transaction = db.transaction(["customers"], "readwrite");
var objectStore = transaction.objectStore("customers");
# 遍歷存儲數據
for (var i in customerData) {
  var request = objectStore.add(customerData[i]);
  request.onsuccess = function(event) {
    // success, done?
  };
}

事宜在豎立的時候不單單議可以制訂實行的情勢,還可以指定本次事宜可以影響的 ObjectStore 範圍,細緻細節就是在第一個 transaction 參數內里傳入的是一個數據,然後經由過程 objectStore() 要領翻開多個 OS 舉行操縱,以下 [代碼3]。

[代碼3]
var tx = db.transaction(["books","person"], "readonly");
var books = tx.objectStore("books");
var person = tx.objectStore("person");

操縱數據

完成了事宜的豎立以後,我們就可以正式的最先舉行數據的交互操縱了,也就是寫我們細緻的營業邏輯。以下 [代碼1],一個完全數據事宜的操縱。

[代碼1]
var tx = db.transaction("books", "readwrite");
var store = tx.objectStore("books");

store.put({title: "Quarry Memories", author: "Fred", isbn: 123456});
store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567});
store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678});

tx.oncomplete = function() {
  // All requests have succeeded and the transaction has committed.
};

經由過程 objectStore 回調取得的 IDBObjectStore 對象,我們就可以舉行一些列的增刪查改操縱了。可以參考 [代碼2]。細緻的可以參考文末的 appendix

[代碼2]
  [NewObject] IDBRequest put(any value, optional any key);
  [NewObject] IDBRequest add(any value, optional any key);
  [NewObject] IDBRequest delete(any query);

索引數據

索引數據是一切數據庫內里最主要的一個。這裏,我們可以運用游標,index 來做。比方,經由過程 index 來疾速索引 key 值,參考 [代碼1]。

[代碼1]
var index = objectStore.index("name");
index.get("Donna").onsuccess = function(event) {
  alert("Donna's SSN is " + event.target.result.ssn);
};

更細緻的內容,可以參考下文 數據索引體式格局

keyPath 和 key Generator

何謂 keyPath 和 keyGenerator 應當算是 IndexedDB 內里比較難以明白的觀點。簡樸來講,IndexedDB 在豎立 Store 的時候,必需保證內里的數據是唯一的,那末得須要像別的數據庫一樣設置一個 primary Key 來辨別差別數據。而 keyPath 和 Generator 就是兩種差別的設置 key 的體式格局。

設置 keyPath

# 設置預先須要寄存的數據

const customerData = [
  { ssn: "444-44-4444", name: "Bill", age: 35, email: "bill@company.com" },
  { ssn: "555-55-5555", name: "Donna", age: 32, email: "donna@home.org" }
];

# 經由過程 keyPath 設置 Primary Key
var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });

因為 ssn 在該數據集是唯一的,所以,我們可以應用它來作為 keyPath 保證 unique 的特徵。或許,可以設置為自增的鍵值,比方 id++ 相似的。

upgradeDb.createObjectStore('logs', {keyPath: 'id', autoIncrement:true});

運用 generator

generator 會每次在增加數據時,自動豎立一個 unique value。這個 unique value 是和你的現實數據是離開的。內里直接經由過程 autoIncrement:true 來設置即可。

upgradeDb.createObjectStore('notes', {autoIncrement:true});

indexDB 翻開注重事項

搜檢是不是支撐 indexDB

if (!('indexedDB' in window)) {
  console.log('This browser doesn\'t support IndexedDB');
  return;
}

版本更新: indexDB

在天生一個 indexDB 實例時,須要手動指定一個版本號。而最經常運用的

idb.open('test-db7', 2, function(upgradeDb) {})

如許會形成一個題目,比方上線過程當中,用戶A第一次要求返回了新版本的網頁,銜接了版本2。以後又革新網頁命中了另一台未上線的机械,銜接了舊版本1 失足。主要緣由是:

indexedDB API 中不允許數據庫中的數據倉庫在統一版本中發生變化. 而且當前 DB 版本不能和低版本的 version 銜接。

比方,你一最先定義的 DB 版本內容為:

# 版本肯定義的內容
db.version(1).stores({friends: "++id,name"});

# 版本二修正組織為:
db.version(2).stores({friends: "++id,name,shoeSize"});

假如此時,用戶先翻開了 version(1),然則背面,又取得的是 version(2) 版本的 HTML,這時候就會湧現 error 的毛病。

參考:

版本更替

版本更新

這個在 IndexDB 是一個很主要的題目。主要緣由在於

indexedDB API 中不允許數據庫中的數據倉庫在統一版本中發生變化. 而且當前 DB 版本不能和低版本的 version 銜接。

上面就可以籠統為一個題目:

你什麼狀況下須要更新 IndexDB 的版本呢?

  1. 該表數據庫內里的 keyPath 時。
  2. 你須要從新設想數據庫表組織時,比方新增 index
# 版本 1 的 DB 設想,有一個主鍵 id 和 index-name
db
.version(1)
.stores({friends: '++id,name'})

# 假如直接想新增一個 key,比方 male,是沒法勝利的
db
.version(1)
.stores({friends: '++id,name,male'})

# 準確方法是直接修正版本號更新
db
.version(2)
.stores({friends: '++id,name,male'})

不過,假如直接修正版本號,會湧現如許一個 case:

  • 因為原始 HTML 更新題目,用戶起首接見的是版本 1 的 A 頁面,然後,接見更新事後的 B 頁面。這時候,IndexDB 勝利更新為高版本。然則,用戶下次又命中了老版本的 A 頁面,此時 A 中照樣銜接低版本的 IndexDB ,就會報錯,致使你接見失利。

解決方法就是,設置過濾,在 open 的時候,手動傳入版本號:

# 翻開版本 1 的數據庫
var dbPromise = idb.open('db1', 1, function(upgradeDb){...})

# 翻開版本 2 的數據庫
var dbPromise = idb.open('db2', 2, function(upgradeDb){...})

不過,如許又會形成別的一個題目,即,數據遷徙(老版本數據,不能夠不要吧)。這裏,IndexDB 會有一個 updateCallback 給你觸發,你可以直接在內里做相干的數據遷徙處置懲罰。

var dbPromise = idb.open('test-db7', 2, function(upgradeDb) {
  switch (upgradeDb.oldVersion) {
    case 0:
      upgradeDb.createObjectStore('store', {keyPath: 'name'});
    case 1:
      var peopleStore = upgradeDb.transaction.objectStore('store');
      peopleStore.createIndex('price', 'price');
  }
});

在運用的時候,肯定要注重 DB 版本的晉級處置懲罰,比方有如許一個 case,你的版本已是 3,不過,你須要處置懲罰版本二的數據:

# 將版本二 中的 name 拆分為  firstName 和 lastName
db.version(3).stores({friends: "++id,shoeSize,firstName,lastName"}).upgrade(function(t) {
    
    return t.friends.toCollection().modify(function(friend) {
        // Modify each friend:
        friend.firstName = friend.name.split(' ')[0];
        friend.lastName = friend.name.split(' ')[1];
        delete friend.name;
    });
});

關於存在版本 2 數據庫的用戶來講是 OK 的,然則關於某些還沒有接見過你數據庫的用戶來講,這無疑就報錯了。解決方法有:

  • 保存每一個版本時,豎立的字段和 stores
  • 在更新 callback 內里,對處置懲罰的數據推斷是不是存在即可。

在 Dexie.js DB 數據庫中,須要你保存每次 DB 豎立的要領,現實上是經由過程 增加 swtich case ,來完成每一個版本的更新:

# Dexie.js 保存 DB 數據庫
db.version(1).stores({friends: "++id,name"});
db.version(2).stores({friends: "++id,name,shoeSize"});
db.version(3).stores({friends: "++id,shoeSize,firstName,lastName"}).upgrade(...)

# 內部道理,直接增加 switch case 完成版本更新
var dbPromise = idb.open('test-db7', 2, function(upgradeDb) {
  switch (upgradeDb.oldVersion) {
    case 0:
      upgradeDb.createObjectStore('store', {keyPath: 'name'});
    case 1:
      var peopleStore = upgradeDb.transaction.objectStore('store');
      peopleStore.createIndex('price', 'price');
  }
});

假如碰到一個頁面翻開,然則別的一個頁面拉取到新的代碼舉行更新時,這個時候還須要將低版本 indexedDB 舉行顯式的封閉。細緻操縱方法就是監聽 onversionchange 事宜,當版本晉級時,關照當前 DB 舉行封閉,然後在新的頁面舉行更新操縱。

openReq.onupgradeneeded = function(event) {
  // 一切別的數據庫都已被關掉了,直接更新代碼
  db.createObjectStore(/* ... */);
  db.onversionchange = function(event) {
    db.close();
  };

}  
  

末了,更新是另有幾個注重事項:

  • 版本更新不能轉變 primary key
  • 回退代碼時,萬萬注重版本是不是已更新。不然,只能增量更新,從新修正版本號來修復。

存儲加密特徵

有時候,我們存儲時,想取得一個由一串 String 天生的 hash key,那在 Web 上應當怎樣完成呢?

這裏可以直接應用 Web 上已完成的 WebCrypto,為了完成上述需求,我們可以直接應用內里的 digest 要領即可。這裏 MDN 上,已有現成的方法,我們直接運用即可。

參考:

WebCrypto 加密手腕

存儲上限值

基礎限製為:

瀏覽器限制
Chrome可用空間 < 6%
Firebox可用空間 < 10%
Safari< 50MB
IE10< 250MB

逐出戰略為:

瀏覽器逐出政策
Chrome在 Chrome 耗盡空間后採納 LRU 戰略
Firebox在全部磁盤已裝滿時採納 LRU 戰略
Safari無逐出
Edge無逐出

參考:

存儲上限值
瀏覽器內核存儲上限值處置懲罰

數據索引體式格局

在數據庫中除了基礎的 CRUD 外,一個高效的索引架構,則是內里的重中之重。在 indexedDB 中,我們一共可以經由過程三種體式格局來索引數據:

  • 牢固的 key 值
  • 索引外鍵(index)
  • 游標(cursor)

牢固 key 索引

IDBObjectStore 供應給了我們直接經由過程 primaryKey 來索引數據,參考 [代碼1],這類體式格局須要我們一最先就曉得目的的 key 內容。固然,也可以經由過程 getAll 悉數索引數據。

[代碼1]
  [NewObject] IDBRequest get(any query);
  [NewObject] IDBRequest getKey(any query);
  [NewObject] IDBRequest getAll(optional any query,
                                optional [EnforceRange] unsigned long count);
  [NewObject] IDBRequest getAllKeys(optional any query,
                                    optional [EnforceRange] unsigned long count);

比方,我們經由過程 primaryKey 取得一條細緻的數據:

db.transaction("customers").objectStore("customers").get("id_card_1118899").onsuccess = function(event) {
    // data is event.target.result.name
};

也可以 fetch 全部 Object Store 的數據。這些場景用途比較少,這裏就不過量解說。我們主要來相識一下 index 的索引體式格局。

index 索引

假如想要查詢某個數據,直接經由過程全部對象來舉行遍歷的話,如許做機能耗時是非常大的。假如我們連繫 index 來將 key 加以分類,就可以很疾速的完成指定數據的索引。這裏,我們可以直接應用 IDBObjectStore 上面的 index() 要領來獵取指定 index 的值,細緻要領可以參考 [代碼1]。

[代碼1]
 IDBIndex index(DOMString name);

該要領會直接返回一個 IDBIndex 對象。這你也可以明白為一個相似 ObjectStore 的微型 index 數據內容。接着,我們可以運用 get() 要領來取得指定 index 的數據,參考[代碼2]。

[代碼2]
var index = objectStore.index("name");
index.get("Donna").onsuccess = function(event) {
  alert("Donna's SSN is " + event.target.result.ssn);
};

運用 get 要領不論你的 index 是不是是 unique 的都邑只會返回第一個數據。假如想取得多個數據的話,可以運用 getAll(key) 來做。經由過程 getAll() 取得的回調函數,直接經由過程 event.target.result 可以取得對應的 value 內容。

objectStore.getAll().onsuccess = function(event) {
      printf(event.target.result); // Array
    };

除了經由過程 getAll() 取得一切數據外,還可以採納更高效的 cursor 要領遍歷取得的數據。

參考:

getAll() 和 openCursor 實例

游標索引

所謂的游標,人人內心應當可以有一個開端的印象,就像我們物理尺子上的誰人東西,可以自在的挪動,來標識指向的對象內容。cursor 內里有兩个中心的要領:

  • advance(count): 將當前游標位置向前挪動 count 位置
  • continue(key): 將當前游標位置挪動到指定 key 的位置,假如沒供應 key 則代表的挪動下一個位置。

比方,我們運用 cursor 來遍歷 Object Store 的細緻數據。

objectStore.openCursor().onsuccess = function(event) {
    var cursor = event.target.result;
    if(cursor) {
        // cursor.key 
        // cursor.value
      cursor.continue();
    } else {
      console.log('Entries all displayed.');
    }
  };

一般,游標可以用來遍歷兩個範例的數據,一個是 ObjectStore、一個是 Index。

  • Object.store: 假如在該對象上運用游標,那末會依據 primaryKey 遍歷全部數據,注重,這裏不會存在反覆的狀況,因為 primaryKey 是唯一的。
  • index: 在 index 上運用游標的話,會以當前的 index 來舉行遍歷,个中能夠會存在反覆的徵象。

在 IDBObjectStore 對象上有兩種要領來翻開游標:

  • openCursor: 遍歷的對象是 細緻的數據值,最經常運用的要領
  • openKeyCursor: 遍歷的對象是 數據 key 值

這裏,我們經由過程 openCursor 來直接翻開一個 index 數據集,然後舉行遍歷。

PersonIndex.openCursor().onsuccess = function(event) {
  var cursor = event.target.result;
  if (cursor) {
    customers.push(cursor.value);
    cursor.continue();
  }
  else {
    alert("Got all customers: " + customers);
  }
};

在游標中,還供應給了一個 updatedelete 要領,我們可以用它來舉行數據的更新操縱,不然的話就直接運用 ObjectStore 供應的 put 要領。

游標內里我們還可以限制其遍歷的範圍和方向。這個設置是我們直接在 openCursor() 要領內里傳參完成的,該要領的組織函數參考 [代碼1]。他內里可以傳入兩個參數,第一個用來指定範圍,第二個用來指定 cursor 挪動的方向。

[代碼1]
IDBRequest openCursor(optional any query,
                                    optional IDBCursorDirection direction = "next");

假如須要對 cursor 設置範圍的話,就須要運用到 IDBKeyRange 這個對象,運用榜樣可以參考 [代碼2]。IDBKeyRange 內里 key 參考的對象 因運用者的差別而差別。假如是針對 ObjectStore 的話,則是針對 primaryKey,假如是針對 Index 的話,則是針對當前的 indexKey

/ 婚配一切在 “Bill” 前面的, 然則不須要包括 "Bill"
var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true);

比方,我們這裏對 PersonIndex 設置一個 index 範圍,即,索引 在 villainhrjimmyVV 之間的數據鳩合。

# 都包括 villainhr 和 jimmyVV 的數據
var boundKeyRange = IDBKeyRange.bound("villainhr", "jimmyVV", true, true);

 PersonIndex.openCursor(boundKeyRange).onsuccess = function(event) {
  var cursor = event.target.result;
  if (cursor) {
    // Do something with the matches.
    cursor.continue();
  }
};

假如你還想設置遍歷的方向和是不是消除反覆數據,還可以依據 [代碼2] 的羅列範例來設置。比方,在 [代碼3] 中,我們轉變默許的 cursor 遍曆數據的方向為 prev,從末端最先。

[代碼2]
enum IDBCursorDirection {
  "next",
  "nextunique",
  "prev",
  "prevunique"
};

[代碼3]
objectStore.openCursor(null, IDBCursor.prev).onsuccess = function(event) {
  var cursor = event.target.result;
  if (cursor) {
    // cursor.value 
    cursor.continue();
  }
};

事宜讀取機能

在 indexDB 內里的讀寫悉數是基於 transaction 情勢來的。也就是 IDBDataBase 內里的 transaction 要領,以下 [代碼1]。一切的讀寫都可以比作在 transaction 作用域下的要求,只要當一切要求完成以後,該次 transaction 才會見效,不然就會拋出非常或許毛病。transaction 會依據監聽 error,abort,以及 complete 三個事宜來完成全部事宜的流程治理,參考[代碼2]。

[代碼1]
  [NewObject] IDBTransaction transaction((DOMString or sequence<DOMString>) storeNames,
                                         optional IDBTransactionMode mode = "readonly");

[代碼2]
  attribute EventHandler onabort;
  attribute EventHandler oncomplete;
  attribute EventHandler onerror;

比方:

var request = db.transaction(["customers"], "readwrite")
                .objectStore("customers")
                .delete("gg");
request.onsuccess = function(event) {
  // delete, done
};

你可以在 transaction 要領內裏手動傳入 readwrite 或許其他示意事宜的 readonly 參數,來示意本次事宜你會舉行怎樣的操縱。IndexedDB 在初始設想時,就已決議了它的機能題目。

只含有 readonly 情勢的 transaction 可以併發舉行實行

含有 write 情勢的 transaction 必需根據行列 來 實行

這就意味着,假如你運用了 readwrite 情勢的話,那末後續不論是不是是 readonly 都必需守候該次 transaction 完成才行。

經常運用技能

天生 id++ 的主鍵

指定 primaryKey 天生時,是經由過程 createObjectStore 要領來操縱的。有時候,我們會碰到想直接取得一個 key,而且存在於當前數據集合,可以在 options 中同時加上 keyPathautoIncrement 屬性。該 key 的範圍是 [1- $ 2^{53} $],參考 keygenerator key 的大小

db.createObjectStore('table1', {keyPath: 'id', autoIncrement: true});

引薦

瀏覽引薦

indexedDB W3C 文檔
indexedDB 入門
MDN indexedDB 入門

好用庫引薦

idb: 一個 promise 的 DB 庫

Indexed Appendix

  • IndexedDB 數據庫運用key-value鍵值對貯存數據.你可以對對象的某個屬性豎立索引(index)以完成疾速查詢和枚舉排序。.key可以使二進制對象
  • IndexedDB 是事宜情勢的數據庫. IndexedDB API供應了索引(indexes), 表(tables), 指針(cursors)等等, 然則一切這些必需是依賴於某種事宜的。
  • The IndexedDB API 基礎上是異步的.
  • IndexedDB 數據庫的要求都邑包括 onsuccess和onerror事宜屬性。
  • IndexedDB 在效果準備好以後經由過程DOM事宜關照用戶
  • IndexedDB是面向對象的。indexedDB不是用二維表來示意鳩合的關聯型數據庫。這一點非常主要,將影響你設想和豎立你的運用遞次。
  • indexedDB不運用組織化查詢言語(SQL)。它經由過程索引(index)所發生的指針(cursor)來完成查詢操縱,從而使你可以迭代遍歷到效果鳩合。
  • IndexedDB遵照同源(same-origin)戰略

範圍和移除 case

  • 環球多種言語夾雜存儲。國際化支撐不好。須要本身處置懲罰。
  • 和服務器端數據庫同步。你得本身寫同步代碼。
  • 全文搜刮。

在以下狀況下,數據庫能夠被消滅:

  • 用戶要求消滅數據。
  • 瀏覽器處於隱私情勢。末了退出瀏覽器的時候,數據會被消滅。
  • 硬盤等存儲裝備的容量到限。
  • 不準確的
  • 不完全的轉變.

通例觀點

數據庫

  • 數據庫: 一般包括一個或多個 object stores. 每一個數據庫必需包括以下內容:

    • 名字(Name): 它標識了一個特定源中的數據庫,而且在數據庫的全部生命周期內堅持穩定. 此名字可認為恣意字符串值(包括空字符串).
    • 當前版本(version). 當一個數據庫初次豎立時,它的 version 為1,除非別的指定. 每一個數據庫在恣意時候只能有一個 version
  • 對象存儲(object store): 用來承載數據的一個分區.數據以鍵值對情勢被對象存儲永遠持有。在 OS 中,豎立一個 key 可以運用 key generatorkey path

    • key generator: 簡樸來講就是在存儲數據時,主動天生一個 id++ 來辨別每條紀錄。這類狀況下 存儲數據的 key 是和 value 離開舉行存儲的,也就是 (out of line)。
    • key path: 須要用戶主動來設置貯存數據的 key 內容,
    • request: 每次讀寫操縱,可以當作一次 request.
    • transaction: 一系列讀寫要求的鳩合。
    • index: 一個特別的 Object Store,用來索引別的一個 Store 的數據。
  • 細緻數據 key/value

    • key: 這個 key 的值,可以經由過程三種體式格局天生。 a key generator, a key path, 用戶指定的值。而且,這個 key 在當前的 Object Store 是唯一的。一個 key 範例可所以 string, date, float, and array 範例。不過,在老版本的時候,平常只支撐 string or integer。(如今,版本應當都 OK 了)

      - key generator: 相當於以一種 `id++` 的情勢來天生一個 key 值。
      - key path: 當前指定的 key 可以依據 value 內里的內容來指定。內里可認為一些分隔符。
      - 指定的 key:這個就是須要用戶手動來指定天生。
      • value: 可以存儲 boolean, number, string, date, object, array, regexp, undefined, and null。如今還可以存儲 files and blob 對象。

操縱作用域

  • scope:這可以比作 transaction 的作用域,即,一系列 transaction 實行的遞次。該劃定,多個 reading transaction 可以同時實行。然則 writing 則只能列隊舉行。
  • key range: 用來設置掏出數據的 key 的範圍內容。

參考:

原生觀點 IndexedDB

IDBFactory

這實在就是 indexDB 上面掛載的對象。主要 API 以下:

[Exposed=(Window,Worker)]
interface IDBFactory {
  [NewObject] IDBOpenDBRequest open(DOMString name,
                                    optional [EnforceRange] unsigned long long version);
  [NewObject] IDBOpenDBRequest deleteDatabase(DOMString name);

  short cmp(any first, any second);
};

你可以直接經由過程 open 來翻開一個數據庫。經由過程 返回一個 Request 對象,來舉行效果監聽的回調:

var request = indexedDB.open('AddressBook', 15);
request.onsuccess = function(evt) {...};
request.onerror = function(evt) {...};

參考:

IndexDB Factory API

IDBRequest

當你經由過程 open 要領處置懲罰事後,就會取得一個 Request 回調對象。這個就是 IDBRequest 的實例。

[Exposed=(Window,Worker)]
interface IDBRequest : EventTarget {
  readonly attribute any result; // 經由過程 open 翻開事後的 IDBObjectStore 實例 
  readonly attribute DOMException? error;
  readonly attribute (IDBObjectStore or IDBIndex or IDBCursor)? source;
  readonly attribute IDBTransaction? transaction;
  readonly attribute IDBRequestReadyState readyState;

  // Event handlers:
  attribute EventHandler onsuccess;
  attribute EventHandler onerror;
};

enum IDBRequestReadyState {
  "pending",
  "done"
};

[Exposed=(Window,Worker)]
interface IDBOpenDBRequest : IDBRequest {
  // Event handlers:
  attribute EventHandler onblocked;
  attribute EventHandler onupgradeneeded;
};

你可以經由過程 result 取得當前數據庫操縱的效果。假如你翻開更新后的版本號的話,還須要監聽 onupgradeneeded 事宜來完成。最常經由過程 indexedDB.open 碰見的毛病就是 VER_ERR 版本毛病。這表明存儲在磁盤上的數據庫的版本高於你試圖翻開的版本。

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

所以,平常在豎立 IndexDB 時,還須要治理它版本的更新操縱,這裏就須要監聽 onupgradeneeded 來是完成。

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

或許我們可以直接運用 idb 微型庫來完成讀取操縱。

  var dbPromise = idb.open('test-db3', 1, function(upgradeDb) {
    if (!upgradeDb.objectStoreNames.contains('people')) {
      upgradeDb.createObjectStore('people', {keyPath: 'email'});
    }
    if (!upgradeDb.objectStoreNames.contains('notes')) {
      upgradeDb.createObjectStore('notes', {autoIncrement: true});
    }
    if (!upgradeDb.objectStoreNames.contains('logs')) {
      upgradeDb.createObjectStore('logs', {keyPath: 'id', autoIncrement: true});
    }
  });

个中經由過程 onupgradeneeded 回調取得的 event.result 就是 IDBDatabase 的實例,經經常運用來設置 index 和插進去數據。參考下面內容。

參考:

IDBRequest API

IDBDatabase

該對象經經常運用來做 Object Store 和 transaction 的豎立和刪除。該部份是 onupgradeneeded 事宜取得的 event.target.result 對象:

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

細緻 API 內容以下:

[Exposed=(Window,Worker)]
interface IDBDatabase : EventTarget {
  readonly attribute DOMString name;
  readonly attribute unsigned long long version;
  readonly attribute DOMStringList objectStoreNames;

  [NewObject] IDBTransaction transaction((DOMString or sequence<DOMString>) storeNames,
                                         optional IDBTransactionMode mode = "readonly");
  void close();

  [NewObject] IDBObjectStore createObjectStore(DOMString name,
                                               optional IDBObjectStoreParameters options);
  void deleteObjectStore(DOMString name);

  // Event handlers:
  attribute EventHandler onabort;
  attribute EventHandler onclose;
  attribute EventHandler onerror;
  attribute EventHandler onversionchange;
};

dictionary IDBObjectStoreParameters {
  (DOMString or sequence<DOMString>)? keyPath = null;
  boolean autoIncrement = false;
};

假如它經由過程 createObjectStore 要領,那末取得的就是一個 IDBObjectStore 實例對象。假如是 transaction 要領,那末就是 IDBTransaction 對象。

IDBObjectStore

該對象平常是用來豎立 index 和插進去數據運用。

可以參考:

[Exposed=(Window,Worker)]
interface IDBObjectStore {
  attribute DOMString name;
  readonly attribute any keyPath;
  readonly attribute DOMStringList indexNames;
  [SameObject] readonly attribute IDBTransaction transaction;
  readonly attribute boolean autoIncrement;

  [NewObject] IDBRequest put(any value, optional any key);
  [NewObject] IDBRequest add(any value, optional any key);
  [NewObject] IDBRequest delete(any query);
  [NewObject] IDBRequest clear();
  [NewObject] IDBRequest get(any query);
  [NewObject] IDBRequest getKey(any query);
  [NewObject] IDBRequest getAll(optional any query,
                                optional [EnforceRange] unsigned long count);
  [NewObject] IDBRequest getAllKeys(optional any query,
                                    optional [EnforceRange] unsigned long count);
  [NewObject] IDBRequest count(optional any query);

  [NewObject] IDBRequest openCursor(optional any query,
                                    optional IDBCursorDirection direction = "next");
  [NewObject] IDBRequest openKeyCursor(optional any query,
                                       optional IDBCursorDirection direction = "next");

  IDBIndex index(DOMString name);

  [NewObject] IDBIndex createIndex(DOMString name,
                                   (DOMString or sequence<DOMString>) keyPath,
                                   optional IDBIndexParameters options);
  void deleteIndex(DOMString name);
};

dictionary IDBIndexParameters {
  boolean unique = false;
  boolean multiEntry = false;
};

IDBIndex

該對象是用來舉行 Index 索引的操縱對象,內里也會存在 getgetAll 等要領。細緻內容以下:

[Exposed=(Window,Worker)]
interface IDBIndex {
  attribute DOMString name;
  [SameObject] readonly attribute IDBObjectStore objectStore;
  readonly attribute any keyPath;
  readonly attribute boolean multiEntry;
  readonly attribute boolean unique;

  [NewObject] IDBRequest get(any query);
  [NewObject] IDBRequest getKey(any query);
  [NewObject] IDBRequest getAll(optional any query,
                                optional [EnforceRange] unsigned long count);
  [NewObject] IDBRequest getAllKeys(optional any query,
                                    optional [EnforceRange] unsigned long count);
  [NewObject] IDBRequest count(optional any query);

  [NewObject] IDBRequest openCursor(optional any query,
                                    optional IDBCursorDirection direction = "next");
  [NewObject] IDBRequest openKeyCursor(optional any query,
                                       optional IDBCursorDirection direction = "next");
};

參考:

idb 開源庫,微型代碼庫
treo 開源庫
dexie.js 開源庫
indexeddb
原生觀點 IndexedDB

也迎接人人關注我的民眾號:前端小吉米 取得一手的手藝文章以及未來手藝的生長內容。

《IndexedDB 打造靠譜 Web 離線數據庫》

    原文作者:villainhr
    原文地址: https://segmentfault.com/a/1190000014950564
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞