IndexedDB_Web 离线数据库
IndexedDB_Web 離線數(shù)據庫
本文會從頭剖析一下 IndexedDB 在前端里面的應用的發(fā)展。
indexedDB 目前在前端慢慢得到普及和應用。它正朝著前端離線數(shù)據庫技術的步伐前進。以前一開始是 manifest、localStorage、cookie 再到 webSQL,現(xiàn)在 indexedDB 逐漸被各大瀏覽器認可。我們也可以針對它來進行技術上創(chuàng)新的開發(fā)。比如,現(xiàn)在小視頻非常流行,那么我們可以在用戶觀看時,通過 cacheStorage 緩存,然后利用 WebRTC 技術實現(xiàn) P2P 分發(fā)的控制,不過需要注意,一定要合理利用大小,不然后果真的很嚴重。
indexedDB 的整體架構,是由一系列單獨的概念串聯(lián)而成,全部概念如下列表。一眼看去會發(fā)現(xiàn)沒有任何邏輯,不過,這里我順手畫了一幅邏輯圖,中間會根據 函數(shù) 的調用而相互串聯(lián)起來
- IDBRequest
- IDBFactory
- IDBDatabase
- IDBObjectStore
- IDBIndex
- IDBKeyRange
- IDBCursor
- IDBTransaction
整體邏輯圖如下:
本文講解流程:
下文主要介紹了 indexedDB 的基本概念,以及在實際應用中的實操代碼。
- indexedDB 基礎概念。在 indexedDB 里面會根據索引 index 來進行整體數(shù)據結構的劃分。
- indexedDB 數(shù)據庫的更新是一個非常蛋疼的事情,因為,Web 的靈活性,你既需要做好向上版本的更新,也需要完善向下版本的容錯性。
- indexedDB 高效索引機制,在內部,indexedDB 已經提供了 index、cursor等高效的索引機制,推薦不要直接將所有數(shù)據都取回來,再進行篩選,而是直接利用 cursor 進行。
- 最后推薦幾個常用庫
離線存儲
IndexedDB 可以存儲非常多的數(shù)據,比如 Object,files,blobs 等,里面的存儲結構是根據 Database 來進行存儲的。每個 DB 里面可以有不同的 object stores。具體結構如下圖:
并且,我們可以給 key 設定相關特定的值,然后在索引的時候,可以直接通過 key 得到具體的內容。使用 IndexDB 需要注意,其遵循的是同域原則。
indexDB 基本概念
在 indexDB 中,有幾個基本的操作對象:
- Database: 通過 open 方法直接打開,可以得到一個實例的 DB。每個頁面可以創(chuàng)建多個 DB,不過一般都是一個。
- Object store: 這個就是 DB 里面具體存儲的對象。這個可以對應于 SQL 里面的 table 內容。其存儲的結構為:
- index: 有點類似于外鏈,它本身是一種 Object store,主要是用來在本體的 store 中,索引另外 object store 里面的數(shù)據。需要區(qū)別的是,key 和 index 是不一樣的。可以參考: index DEMO,mdn index。如下圖表示:
如下 code 為:
// 創(chuàng)建 index var myIndex = objectStore.index('lName');- transaction: 事務其實就是一系列 CRUD 的集合內容。如果其中一個環(huán)節(jié)失敗了,那么整個事務的處理都會被取消。例如:
- cursor: 主要是用來遍歷 DB 里面的數(shù)據內容。主要是通過 openCursor 來進行控制。
如何使用 IndexDB
上面說了幾個基本的概念。那接下來我們實踐一下 IndexDB。實際上入門 IndexDB 就是做幾個基本的內容
- 打開數(shù)據庫表
- 設置指定的 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 參數(shù)var objectStore = db.createObjectStore("customers", { keyPath: "id",{autoIncrement:true} });// 設置指定索引,并確保唯一性objectStore.createIndex("name", "name", { unique: false });objectStore.createIndex("email", "email", { unique: true });};上面主要做了 3 件事:
- 打開數(shù)據庫表
- 新建 Store,并設置 primary Key
- 設置 index
打開數(shù)據庫表主要就是版本號和名字,沒有太多講的,我們直接從創(chuàng)建 store 開始吧。
創(chuàng)建 Object Store
使用的方法就是 IDBDatabase 上的 createObjectStore 方法。
var objectStore = db.createObjectStore("customers", { keyPath: "id",{autoIncrement:true} });基本函數(shù)構造為:
IDBObjectStore createObjectStore(DOMString name,optional IDBObjectStoreParameters options)dictionary IDBObjectStoreParameters {(DOMString or sequence<DOMString>)? keyPath = null;boolean autoIncrement = false; };- keyPath: 用來設置主鍵的 key,具體區(qū)別可以參考下面的 keyPath 和 generator 的區(qū)別。
- autoIncrement: 是否使用自增 key 的特性。
設置索引 index
在完成 PK(Primary key) 創(chuàng)建完畢后,為了更好的搜索性能我們還需要額外創(chuàng)建 index。這里可以直接使用:
objectStore.createIndex('indexName', 'property', options);- indexName: 設置當前 index 的名字
- property: 從存儲數(shù)據中,指明 index 所指的屬性。
其中,options 有三個選項:
- unique: 當前 key 是否能重復 (最常用)
- multiEntry: 設置當前的 property 為數(shù)組時,會給數(shù)組里面每個元素都設置一個 index 值。
具體可以參考:MDN createIndex Prop 和 googleDeveloper Index。
增刪數(shù)據
在 IndexedDB 里面進行數(shù)據的增刪,都需要在 transaction 中完成。而這個增刪數(shù)據,大家可以理解為一次 request,相當于在一個 transaction 里面管理所有當前邏輯操作的 request。所以,在正式開始進行數(shù)據操作之前,還需要給大家簡單介紹一些如果創(chuàng)建一個事務。
事務的創(chuàng)建
transaction API,如下 [代碼1]。在創(chuàng)建時,你需要手動指定當前 transaction 是那種類型的操作,基本的內容有:
- “readonly”:只讀
- “readwrite”:讀寫
- “versionchange”:這個不能手動指定,會在 upgradeneeded 回調事件里面自動創(chuàng)建。它可以用來修改現(xiàn)有 object store 的結構數(shù)據,比如 index 等。
你可以通過在數(shù)據庫打開之后,通過 IDBDataBase 上的 transaction 方法創(chuàng)建,如 [代碼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"); # 遍歷存儲數(shù)據 for (var i in customerData) {var request = objectStore.add(customerData[i]);request.onsuccess = function(event) {// success, done?}; }事務在創(chuàng)建的時候不僅僅可以制定執(zhí)行的模式,還可以指定本次事務能夠影響的 ObjectStore 范圍,具體細節(jié)就是在第一個 transaction 參數(shù)里面?zhèn)魅氲氖且粋€數(shù)據,然后通過 objectStore() 方法打開多個 OS 進行操作,如下 [代碼3]。
[代碼3] var tx = db.transaction(["books","person"], "readonly"); var books = tx.objectStore("books"); var person = tx.objectStore("person");操作數(shù)據
完成了事務的創(chuàng)建之后,我們就可以正式的開始進行數(shù)據的交互操作了,也就是寫我們具體的業(yè)務邏輯。如下 [代碼1],一個完整數(shù)據事務的操作。
[代碼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);索引數(shù)據
索引數(shù)據是所有數(shù)據庫里面最重要的一個。這里,我們可以使用游標,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); };更詳細的內容,可以參考下文 數(shù)據索引方式。
keyPath 和 key Generator
何謂 keyPath 和 keyGenerator 應該算是 IndexedDB 里面比較難以理解的概念。簡單來說,IndexedDB 在創(chuàng)建 Store 的時候,必須保證里面的數(shù)據是唯一的,那么得需要像其它數(shù)據庫一樣設置一個 primary Key 來區(qū)分不同數(shù)據。而 keyPath 和 Generator 就是兩種不同的設置 key 的方式。
設置 keyPath
# 設置預先需要存放的數(shù)據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 在該數(shù)據集是唯一的,所以,我們可以利用它來作為 keyPath 保證 unique 的特性。或者,可以設置為自增的鍵值,比如 id++ 類似的。
upgradeDb.createObjectStore('logs', {keyPath: 'id', autoIncrement:true});使用 generator
generator 會每次在添加數(shù)據時,自動創(chuàng)建一個 unique value。這個 unique value 是和你的實際數(shù)據是分開的。里面直接通過 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 中不允許數(shù)據庫中的數(shù)據倉庫在同一版本中發(fā)生變化. 并且當前 DB 版本不能和低版本的 version 連接。
比如,你一開始定義的 DB 版本內容為:
# 版本一定義的內容 db.version(1).stores({friends: "++id,name"});# 版本二修改結構為: db.version(2).stores({friends: "++id,name,shoeSize"});如果此時,用戶先打開了 version(1),但是后面,又得到的是 version(2) 版本的 HTML,這時就會出現(xiàn) error 的錯誤。
參考:
- 版本更替
版本更新
這個在 IndexDB 是一個很重要的問題。主要原因在于
indexedDB API 中不允許數(shù)據庫中的數(shù)據倉庫在同一版本中發(fā)生變化. 并且當前 DB 版本不能和低版本的 version 連接。
上面就可以抽象為一個問題:
你什么情況下需要更新 IndexDB 的版本呢?
不過,如果直接修改版本號,會出現(xiàn)這樣一個 case:
- 由于原始 HTML 更新問題,用戶首先訪問的是版本 1 的 A 頁面,然后,訪問更新過后的 B 頁面。這時,IndexDB 成功更新為高版本。但是,用戶下次又命中了老版本的 A 頁面,此時 A 中還是連接低版本的 IndexDB ,就會報錯,導致你訪問失敗。
解決辦法就是,設置過濾,在 open 的時候,手動傳入版本號:
# 打開版本 1 的數(shù)據庫 var dbPromise = idb.open('db1', 1, function(upgradeDb){...})# 打開版本 2 的數(shù)據庫 var dbPromise = idb.open('db2', 2, function(upgradeDb){...})不過,這樣又會造成另外一個問題,即,數(shù)據遷移(老版本數(shù)據,不可能不要吧)。這里,IndexDB 會有一個 updateCallback 給你觸發(fā),你可以直接在里面做相關的數(shù)據遷移處理。
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,不過,你需要處理版本二的數(shù)據:
# 將版本二 中的 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 數(shù)據庫的用戶來說是 OK 的,但是對于某些還沒有訪問過你數(shù)據庫的用戶來說,這無疑就報錯了。解決辦法有:
- 保留每個版本時,創(chuàng)建的字段和 stores
- 在更新 callback 里面,對處理的數(shù)據判斷是否存在即可
在 Dexie.js DB 數(shù)據庫中,需要你保留每次 DB 創(chuàng)建的方法,實際上是通過 添加 swtich case ,來完成每個版本的更新:
# Dexie.js 保留 DB 數(shù)據庫 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 進行顯式的關閉。具體操作辦法就是監(jiān)聽 onversionchange 事件,當版本升級時,通知當前 DB 進行關閉,然后在新的頁面進行更新操作。
openReq.onupgradeneeded = function(event) {// 所有其它數(shù)據庫都已經被關掉了,直接更新代碼db.createObjectStore(/* ... */);db.onversionchange = function(event) {db.close();};}最后,更新是還有幾個注意事項:
- 版本更新不能改變 primary key
- 回退代碼時,千萬注意版本是否已經更新。否則,只能增量更新,重新修改版本號來修復。
存儲加密特性
有時候,我們存儲時,想得到一個由一串 String 生成的 hash key,那在 Web 上應該如何實現(xiàn)呢?
這里可以直接利用 Web 上已經實現(xiàn)的 WebCrypto,為了實現(xiàn)上述需求,我們可以直接利用里面的 digest 方法即可。這里 MDN 上,已經有現(xiàn)成的辦法,我們直接使用即可。
參考:
WebCrypto 加密手段
存儲上限值
基本限制為:
| Chrome | 可用空間 < 6% |
| Firebox | 可用空間 < 10% |
| Safari | < 50MB |
| IE10 | < 250MB |
逐出策略為:
| Chrome | 在 Chrome 耗盡空間后采用 LRU 策略 |
| Firebox | 在整個磁盤已裝滿時采用 LRU 策略 |
| Safari | 無逐出 |
| IE10 | 無逐出 |
參考:
存儲上限值 瀏覽器內核存儲上限值處理
數(shù)據索引方式
在數(shù)據庫中除了基本的 CRUD 外,一個高效的索引架構,則是里面的重中之重。在 indexedDB 中,我們一共可以通過三種方式來索引數(shù)據:
- 固定的 key 值
- 索引外鍵(index)
- 游標(cursor)
固定 key 索引
IDBObjectStore 提供給了我們直接通過 primaryKey 來索引數(shù)據,參考 [代碼1],這種方式需要我們一開始就知道目標的 key 內容。當然,也可以通過 getAll 全部索引數(shù)據。
[代碼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 得到一條具體的數(shù)據:
db.transaction("customers").objectStore("customers").get("id_card_1118899").onsuccess = function(event) {// data is event.target.result.name };也可以 fetch 整個 Object Store 的數(shù)據。這些場景用處比較少,這里就不過多講解。我們主要來了解一下 index 的索引方式。
index 索引
如果想要查詢某個數(shù)據,直接通過整個對象來進行遍歷的話,這樣做性能耗時是非常大的。如果我們結合 index 來將 key 加以分類,就可以很快速的實現(xiàn)指定數(shù)據的索引。這里,我們可以直接利用 IDBObjectStore 上面的 index() 方法來獲取指定 index 的值,具體方法可以參考 [代碼1]。
[代碼1]IDBIndex index(DOMString name);該方法會直接返回一個 IDBIndex 對象。這你也可以理解為一個類似 ObjectStore 的微型 index 數(shù)據內容。接著,我們可以使用 get() 方法來獲得指定 index 的數(shù)據,參考[代碼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 的都會只會返回第一個數(shù)據。如果想得到多個數(shù)據的話,可以使用 getAll(key) 來做。通過 getAll() 得到的回調函數(shù),直接通過 event.target.result 可以得到對應的 value 內容。
objectStore.getAll().onsuccess = function(event) {printf(event.target.result); // Array};除了通過 getAll() 得到所有數(shù)據外,還可以采用更高效的 cursor 方法遍歷得到的數(shù)據。
參考:
getAll() 和 openCursor 實例
游標索引
所謂的游標,大家心里應該可以有一個初步的印象,就像我們物理尺子上的那個東西,可以自由的移動,來標識指向的對象內容。cursor 里面有兩個核心的方法:
- advance(count): 將當前游標位置向前移動 count 位置
- continue(key): 將當前游標位置移動到指定 key 的位置,如果沒提供 key 則代表的移動下一個位置。
比如,我們使用 cursor 來遍歷 Object Store 的具體數(shù)據。
objectStore.openCursor().onsuccess = function(event) {var cursor = event.target.result;if(cursor) {// cursor.key // cursor.valuecursor.continue();} else {console.log('Entries all displayed.');}};通常,游標可以用來遍歷兩個類型的數(shù)據,一個是 ObjectStore、一個是 Index。
- Object.store: 如果在該對象上使用游標,那么會根據 primaryKey 遍歷整個數(shù)據,注意,這里不會存在重復的情況,因為 primaryKey 是唯一的。
- index: 在 index 上使用游標的話,會以當前的 index 來進行遍歷,其中可能會存在重復的現(xiàn)象。
在 IDBObjectStore 對象上有兩種方法來打開游標:
- openCursor: 遍歷的對象是 具體的數(shù)據值,最常用的方法
- openKeyCursor: 遍歷的對象是 數(shù)據 key 值
這里,我們通過 openCursor 來直接打開一個 index 數(shù)據集,然后進行遍歷。
PersonIndex.openCursor().onsuccess = function(event) {var cursor = event.target.result;if (cursor) {customers.push(cursor.value);cursor.continue();}else {alert("Got all customers: " + customers);} };在游標中,還提供給了一個 update 和 delete 方法,我們可以用它來進行數(shù)據的更新操作,否則的話就直接使用 ObjectStore 提供的 put 方法。
游標里面我們還可以限定其遍歷的范圍和方向。這個設置是我們直接在 openCursor() 方法里面?zhèn)鲄⑼瓿傻?#xff0c;該方法的構造函數(shù)參考 [代碼1]。他里面可以傳入兩個參數(shù),第一個用來指定范圍,第二個用來指定 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 范圍,即,索引 在 villainhr 和 jimmyVV 之間的數(shù)據集合。
# 都包括 villainhr 和 jimmyVV 的數(shù)據 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();} };如果你還想設置遍歷的方向和是否排除重復數(shù)據,還可以根據 [代碼2] 的枚舉類型來設置。比如,在 [代碼3] 中,我們改變默認的 cursor 遍歷數(shù)據的方向為 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 會根據監(jiān)聽 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 參數(shù),來表示本次事務你會進行如何的操作。IndexedDB 在初始設計時,就已經決定了它的性能問題。
只含有 readonly 模式的 transaction 可以并發(fā)進行執(zhí)行 含有 write 模式的 transaction 必須按照隊列 來 執(zhí)行這就意味著,如果你使用了 readwrite 模式的話,那么后續(xù)不管是不是 readonly 都必須等待該次 transaction 完成才行。
常用技巧
生成 id++ 的主鍵
指定 primaryKey 生成時,是通過 createObjectStore 方法來操作的。有時候,我們會遇到想直接得到一個 key,并且存在于當前數(shù)據集中,可以在 options 中同時加上 keyPath 和 autoIncrement 屬性。該 key 的范圍是 [1- ],參考 keygenerator key 的大小
db.createObjectStore('table1', {keyPath: 'id', autoIncrement: true});推薦
閱讀推薦
indexedDB W3C 文檔 indexedDB 入門 MDN indexedDB 入門
好用庫推薦
idb: 一個 promise 的 DB 庫
Indexed Appendix
- IndexedDB 數(shù)據庫使用key-value鍵值對儲存數(shù)據.你可以對對象的某個屬性創(chuàng)建索引(index)以實現(xiàn)快速查詢和列舉排序。.key可以使二進制對象
- IndexedDB 是事務模式的數(shù)據庫. IndexedDB API提供了索引(indexes), 表(tables), 指針(cursors)等等, 但是所有這些必須是依賴于某種事務的。
- The IndexedDB API 基本上是異步的.
- IndexedDB 數(shù)據庫的請求都會包含 onsuccess和onerror事件屬性。
- IndexedDB 在結果準備好之后通過DOM事件通知用戶
- IndexedDB是面向對象的。indexedDB不是用二維表來表示集合的關系型數(shù)據庫。這一點非常重要,將影響你設計和建立你的應用程序。
- indexedDB不使用結構化查詢語言(SQL)。它通過索引(index)所產生的指針(cursor)來完成查詢操作,從而使你可以迭代遍歷到結果集合。
- IndexedDB遵循同源(same-origin)策略
局限和移除 case
- 全球多種語言混合存儲。國際化支持不好。需要自己處理。
- 和服務器端數(shù)據庫同步。你得自己寫同步代碼。
- 全文搜索。
在以下情況下,數(shù)據庫可能被清除:
- 用戶請求清除數(shù)據。
- 瀏覽器處于隱私模式。最后退出瀏覽器的時候,數(shù)據會被清除。
- 硬盤等存儲設備的容量到限。
- 不正確的
- 不完整的改變.
常規(guī)概念
數(shù)據庫
- 數(shù)據庫: 通常包含一個或多個 object stores. 每個數(shù)據庫必須包含以下內容:
- 名字(Name): 它標識了一個特定源中的數(shù)據庫,并且在數(shù)據庫的整個生命周期內保持不變. 此名字可以為任意字符串值(包括空字符串).
- 當前版本(version). 當一個數(shù)據庫首次創(chuàng)建時,它的 version 為1,除非另外指定. 每個數(shù)據庫在任意時刻只能有一個 version
- 對象存儲(object store): 用來承載數(shù)據的一個分區(qū).數(shù)據以鍵值對形式被對象存儲永久持有。在 OS 中,創(chuàng)建一個 key 可以使用 key generator 和 key path。
- key generator: 簡單來說就是在存儲數(shù)據時,主動生成一個 id++ 來區(qū)分每條記錄。這種情況下 存儲數(shù)據的 key 是和 value 分開進行存儲的,也就是 (out of line)。
- key path: 需要用戶主動來設置儲存數(shù)據的 key 內容
- request: 每次讀寫操作,可以當做一次 request.
- transaction: 一系列讀寫請求的集合。
- index: 一個特殊的 Object Store,用來索引另外一個 Store 的數(shù)據。
- 具體數(shù)據 key/value
- key: 這個 key 的值,可以通過三種方式生成。 a key generator, a key path, 用戶指定的值。并且,這個 key 在當前的 Object Store 是唯一的。一個 key 類型可以是 string, date, float, and array 類型。不過,在老版本的時候,一般只支持 string or integer。(現(xiàn)在,版本應該都 OK 了)
- key generator: 相當于以一種 id++ 的形式來生成一個 key 值。
- key path: 當前指定的 key 可以根據 value 里面的內容來指定。里面可以為一些分隔符。
- 指定的 key:這個就是需要用戶手動來指定生成。
- value: 可以存儲 boolean, number, string, date, object, array, regexp, undefined, and null。現(xiàn)在還可以存儲 files and blob 對象。
- key: 這個 key 的值,可以通過三種方式生成。 a key generator, a key path, 用戶指定的值。并且,這個 key 在當前的 Object Store 是唯一的。一個 key 類型可以是 string, date, float, and array 類型。不過,在老版本的時候,一般只支持 string or integer。(現(xiàn)在,版本應該都 OK 了)
操作作用域
- scope:這可以比作 transaction 的作用域,即,一系列 transaction 執(zhí)行的順序。該規(guī)定,多個 reading transaction 能夠同時執(zhí)行。但是 writing 則只能排隊進行。
- key range: 用來設置取出數(shù)據的 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 來打開一個數(shù)據庫。通過 返回一個 Request 對象,來進行結果監(jiān)聽的回調:
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 得到當前數(shù)據庫操作的結果。如果你打開更新后的版本號的話,還需要監(jiān)聽 onupgradeneeded 事件來實現(xiàn)。最常通過 indexedDB.open 遇見的錯誤就是 VER_ERR 版本錯誤。這表明存儲在磁盤上的數(shù)據庫的版本高于你試圖打開的版本。
db.onerror = function(event) {// Generic error handler for all errors targeted at this database's// requests!alert("Database error: " + event.target.errorCode); };所以,一般在創(chuàng)建 IndexDB 時,還需要管理它版本的更新操作,這里就需要監(jiān)聽 onupgradeneeded 來是實現(xiàn)。
request.onupgradeneeded = function(event) { // 更新對象存儲空間和索引 .... };或者我們可以直接使用 idb 微型庫來實現(xiàn)讀取操作。
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 和插入數(shù)據。參考下面內容。
參考:
IDBRequest API
IDBDatabase
該對象常常用來做 Object Store 和 transaction 的創(chuàng)建和刪除。該部分是 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
該對象一般是用來創(chuàng)建 index 和插入數(shù)據使用。
可以參考:
[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 索引的操作對象,里面也會存在 get 和 getAll 等方法。詳細內容如下:
[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 离线数据库的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UVA - 12412 A
- 下一篇: Problem G: C语言习题 医生值