在知乎和我在尋常事情中,經常會看到一個題目:
前端如今還火嗎?
這個我只想說:
袖手旁觀的人永遠沒法邃曉起火的緣由,只要置身風暴,才找到風眼之地點 ——『秦時明月』
你 TM 看都不看前端如今的生長,怎樣去評判前端火不火,我該不該嘗試一下其他方面的內容呢?本人為啥為這麼熱衷於新的手藝呢?主要緣由在於,恐怕會被某一項顛覆性的內容鐫汰掉,從前沿範疇落伍下來。說句人話就是:窮,所以只能學了...
。所以本文會從新理會一下 IndexedDB
在前端內里的運用的生長。
indexedDB 現在在前端逐步取得提高和運用。它正朝着前端離線數據庫手藝的步調行進。之前一最先是 manifest、localStorage、cookie 再到 webSQL,如今 indexedDB 逐步被各大瀏覽器承認。我們也可以針對它來舉行手藝上立異的開闢。比方,如今小視頻非常盛行,那末我們可以在用戶寓目時,經由過程 cacheStorage 緩存,然後應用 WebRTC 手藝完成 P2P 分發的掌握,不過須要注重,肯定要合理應用大小,不然效果真的很嚴重。
indexedDB 的團體架構,是由一系列零丁的觀點串連而成,悉數觀點以下列表。一眼看去會發明沒有任何邏輯,不過,這裏我隨手畫了一幅邏輯圖,中心會依據 函數 的挪用而互相串連起來。
- IDBRequest
- IDBFactory
- IDBDatabase
- IDBObjectStore
- IDBIndex
- IDBKeyRange
- IDBCursor
- IDBTransaction
團體邏輯圖以下:
TL;DR
下文主要引見了 indexedDB 的基礎觀點,以及在現實運用中的實操代碼。
- indexedDB 基礎觀點。在 indexedDB 內里會依據索引 index 來舉行團體數據組織的分別。
- indexedDB 數據庫的更新是一個非常蛋疼的事變,因為,Web 的靈活性,你既須要做好向上版本的更新,也須要完美向下版本的容錯性。
- indexedDB 高效索引機制,在內部,indexedDB 已供應了
index
、cursor
等高效的索引機制,引薦不要直接將一切數據都取回來,再舉行挑選,而是直接應用cursor
舉行。 - 末了引薦幾個經常運用庫
離線存儲
IndexedDB 可以存儲非常多的數據,比方 Object,files,blobs 等,內里的存儲組織是依據 Database 來舉行存儲的。每一個 DB 內里可以有差別的 object stores。細緻組織以下圖:
而且,我們可以給 key
設定相干特定的值,然後在索引的時候,可以直接經由過程 key 取得細緻的內容。運用 IndexDB 須要注重,其遵照的是同域準繩。
indexDB 基礎觀點
在 indexDB 中,有幾個基礎的操縱對象:
- Database: 經由過程
open
要領直接翻開,可以取得一個實例的 DB。每一個頁面可以豎立多個 DB,不過平常都是一個。
idb.open(name, version, upgradeCallback)
- Object store: 這個就是 DB 內里細緻存儲的對象。這個可以對應於 SQL 內里的 table 內容。其存儲的組織為:
- index: 有點相似於外鏈,它本身是一種 Object store,重假如用來在本體的 store 中,索引別的 object store 內里的數據。須要辨別的是,key 和 index 是不一樣的。可以參考: index DEMO,mdn index。以下圖示意:
以下 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 Prop 和 googleDeveloper 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 的版本呢?
- 該表數據庫內里的
keyPath
時。 - 你須要從新設想數據庫表組織時,比方新增 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 上,已有現成的方法,我們直接運用即可。
參考:
存儲上限值
基礎限製為:
瀏覽器 | 限制 |
---|---|
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
要領遍歷取得的數據。
參考:
游標索引
所謂的游標,人人內心應當可以有一個開端的印象,就像我們物理尺子上的誰人東西,可以自在的挪動,來標識指向的對象內容。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);
}
};
在游標中,還供應給了一個 update
和 delete
要領,我們可以用它來舉行數據的更新操縱,不然的話就直接運用 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 範圍,即,索引 在 villainhr
和 jimmyVV
之間的數據鳩合。
# 都包括 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 中同時加上 keyPath
和 autoIncrement
屬性。該 key 的範圍是 [1- $ 2^{53} $],參考 keygenerator key 的大小
db.createObjectStore('table1', {keyPath: 'id', autoIncrement: true});
引薦
瀏覽引薦
indexedDB W3C 文檔
indexedDB 入門
MDN indexedDB 入門
好用庫引薦
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 generator
和key 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 的範圍內容。
參考:
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) {...};
參考:
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 和插進去數據。參考下面內容。
參考:
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 索引的操縱對象,內里也會存在 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
也迎接人人關注我的民眾號:前端小吉米 取得一手的手藝文章以及未來手藝的生長內容。