前端儲存
我們都知道在前端開發當中,有時會因為某些需求,要將一些資料儲存在前端本地當中.比如說:為了優化效能,將一些常用的資料存在本地,這樣以後需要的時候直接從本地拿,不需要再向後端進行請求.還有就是為了防止CSRF攻擊,後端給前端一個token,前端就需要將這個token存在本地.之後每次請求都需要帶上這個token.等等不一而足.
而這些需求就不油避免的造就一個前端的發展方向--前端儲存
在前端的'上古時代'裡,我們前端想要儲存資料,只有一種方式,那就是Cookie
.但是Cookie
雖然可以做前端儲存方案,但是卻也有著很多侷限性.首先它的儲存空間大小隻有4K,其次它的儲存有效時間有限制,然後存在Cookie
中的資料,在你每次進行請求的時候都會將它帶上.使得每次的請求資料都會無意義的增大.最後,也是最重要的一點.Cookie
設計之初就不是就是讓我們前端存資料用的.它只是為了讓網站驗證使用者身份用的.至於Cookie
的本地儲存功能只是它的一個手段而已.關於這點你們可以看下我的另外一篇文章---在HTML5的時代,重新認識Cookie
綜上所述,使用Cookie
作為前端儲存有這許多缺點,所以經過前端社群的不斷努力,在HTML5中有了真正的前端儲存方案Web Storage
.它分為兩種,一種是永久儲存的localStorage
,一種是會話期間儲存的sessionStorage
.對比Cookie
,Web Storage
的優勢很明顯:
- 儲存空間更大,有5M大小
- 在瀏覽器傳送請求是不會帶上
web Storage
裡的資料- 更加友好的API
- 可以做永久儲存(localStorage).
這一切看起來很完美,但是隨著前端的不斷髮展,web Storage
也有了一些不太合適的地方:
- 隨著web應用程式的不斷髮展,5M的儲存大小對於一些大型的web應用程式來說有些不夠
web Storage
只能儲存string
型別的資料.對於Object
型別的資料只能先用JSON.stringify()
轉換一下在儲存.
基於上述原因,前端社群又提出了瀏覽器資料庫儲存這個概念.而Web SQL Database
和indexedDB(索引資料庫)
是對這個概念的實現.其中Web SQL Database
在目前來說基本已經被放棄.所以目前主流的瀏覽器資料庫的實現就是indexedDB(索引資料庫)
.也就是我們要介紹的 新一代的前端儲存方案--indexedDB
什麼是indexedDB
indexedDB的介紹
IndexedDB 是一種使用瀏覽器儲存大量資料的方法.它創造的資料可以被查詢,並且可以離線使用. IndexedDB對於那些需要儲存大量資料,或者是需要離線使用的程式是非常有效的解決方法. --- MDN
indexedDB的概念
使用IndexedDB,你可以儲存或者獲取資料,使用一個key索引的。 你可以在事務(transaction)中完成對資料的修改。和大多數web儲存解決方案相同,indexedDB也遵從同源協議(same-origin policy). 所以你只能訪問同域中儲存的資料,而不能訪問其他域的。
API包含非同步(asynchronous) API 和同步(synchronous)API兩種。 非同步API適合大多數情況, 同步API必須同 WebWorkers一同使用. 目前,沒有主流瀏覽器支援同步API。 即使同步API被支援了,你也會在大多數的情況使用非同步API。
IndexedDB 是 WebSQL 資料庫的取代品, W3C組織在2010年11月18日廢棄了webSql. IndexedDB 和WebSQL的不同點在於WebSQL 是關係型資料庫(複雜)IndexedDB 是key-value型資料庫(簡單好使).
上面是MDN上對於IndexedDB的介紹.其簡單而言,indexedDB就是一個基於事務操作的key-value型數前端資料庫.其API大多是非同步的
indexedDB的使用
建立一個indexedDB資料庫
const request = indexedDB.open('myDatabase', 1);
request.addEventListener('success', e => {
console.log("連線資料庫成功");
});
request.addEventListener('error', e => {
console.log("連線資料庫失敗");
});
複製程式碼
在上面程式碼中我們使用indexedDB.open()
建立一個indexedDB
資料庫.open()
方法接受可以接受兩個引數.第一個是資料庫名,第二個是資料庫的版本號.同時返回一個IDBOpenDBRequest
物件用於運算元據庫.其中對於open()
的第一個引數資料庫名,open()
會先去查詢本地是否已有這個資料庫,如果有則直接將這個資料庫返回,如果沒有,則先建立這個資料庫,再返回.對於第二個引數版本號,則是一個可選引數,如果不傳,預設為1.但如果傳入就必須是一個整數.
在通過對indexedDB.open()
方法拿到一個資料庫物件IDBOpenDBRequest
我們可以通過監聽這個物件的success
事件和error
事件來執行相應的操作.
建立一個物件倉庫
再有了一個資料庫之後,我們獲取就想要去儲存資料了,但是單隻有資料庫還不夠,我們還需要有物件倉庫(object store).物件倉庫(object store)是indexedDB資料庫的基礎,其類似於MySQL中表的概念.
要建立一個物件倉庫必須在
upgradeneeded
事件中,而upgradeneeded
事件只會在版本號更新的時候觸發.這是因為indexedDB API
中不允許資料庫中的資料倉儲在同一版本中發生變化
const request = indexedDB.open('myDatabase', 2);
request.addEventListener('upgradeneeded', e => {
const db = e.target.result;
const store = db.createObjectStore('Users', {keyPath: 'userId', autoIncrement: false});
console.log('建立物件倉庫成功');
});
複製程式碼
在上述程式碼中我們監聽upgradeneeded
事件,並在這個事件觸發時使用createObjectStore()
方法建立了一個物件倉庫.createObjectStore()
方法接受兩個引數,第一個是物件倉庫的名字,在同一資料庫中,倉庫名不能重複.第二個是可選引數.用於指定資料的主鍵,以及是否自增主鍵.
建立事務
OK現在我們有了資料庫和物件倉庫了,我們是否就可以儲存資料了了.很抱歉,還是不行.我們還差最後一樣東西----事務.
什麼是事務
一個資料庫事務通常包含了一個序列的對資料庫的讀/寫操作。它的存在包含有以下兩個目的
- 為資料庫操作序列提供了一個從失敗中恢復到正常狀態的方法,同時提供了資料庫即使在異常狀態下仍能保持一致性的方法。
- 當多個應用程式在併發訪問資料庫時,可以在這些應用程式之間提供一個隔離方法,以防止彼此的操作互相干擾。
並非任意的對資料庫的操作序列都是資料庫事務。資料庫事務擁有以下四個特性,習慣上被稱之為ACID特性。
- 原子性(Atomicity):事務作為一個整體被執行,包含在其中的對資料庫的操作要麼全部被執行,要麼都不執行
- 一致性(Consistency):事務應確保資料庫的狀態從一個一致狀態轉變為另一個一致狀態。一致狀態的含義是資料庫中的資料應滿足完整性約束
- 隔離性(Isolation):多個事務併發執行時,一個事務的執行不應影響其他事務的執行
- 永續性(Durability):已被提交的事務對資料庫的修改應該永久儲存在資料庫中
上面是維基百科上對資料庫事務的解釋.簡單來說事務就是用來保證資料庫操作要麼全部成功,要麼全部失敗的一個限制.比如,在修改多條資料時,前面幾條已經成功了.,在中間的某一條是失敗了.那麼在這時,如果是基於事務的資料庫操作,那麼這時資料庫就應該重置前面資料的修改,放棄後面的資料修改.直接返回錯誤,一條資料也不修改.
const request = indexedDB.open('myDatabase', 3);
request.addEventListener('success', e => {
const db = e.target.result;
const tx = db.transaction('Users','readwrite');
});
複製程式碼
上述程式碼中我們使用transaction()來建立一個事務.transaction()
接受兩個引數,第一個是你要操作的物件倉庫名稱,第二個是你建立的事務模式.傳入 readonly
時只能對物件倉庫進行讀操作,無法寫操作.可以傳入readwrite
進行讀寫操作.
運算元據
好了現在有了資料庫,物件倉庫,事務之後我們終於可以儲存資料了.
- add() : 增加資料。接收一個引數,為需要儲存到物件倉庫中的物件。
- put() : 增加或修改資料。接收一個引數,為需要儲存到物件倉庫中的物件。
- get() : 獲取資料。接收一個引數,為需要獲取資料的主鍵值。
- delete() : 刪除資料。接收一個引數,為需要獲取資料的主鍵值。
add 和 put 的作用類似,區別在於 put 儲存資料時,如果該資料的主鍵在資料庫中已經有相同主鍵的時候,則會修改資料庫中對應主鍵的物件,而使用 add 儲存資料,如果該主鍵已經存在,則儲存失敗。
新增資料
const request = indexedDB.open('myDatabase', 3);
request.addEventListener('success', e => {
const db = e.target.result;
const tx = db.transaction('Users','readwrite');
const store = tx.objectStore('Users');
// 儲存資料
const reqAdd = store.add({'userId': 1, 'userName': '李白', 'age': 24});
reqAdd.addEventListener('success', e => {
console.log('儲存成功')
})
});
複製程式碼
獲取資料
const request = indexedDB.open('myDatabase', 3);
request.addEventListener('success', e => {
const db = e.target.result;
const tx = db.transaction('Users','readwrite');
const store = tx.objectStore('Users');
// 獲取資料
const reqGet = store.get(1);
reqGet.addEventListener('success', e => {
console.log(this.result.userName); // 李白
})
});
複製程式碼
刪除資料
const request = indexedDB.open('myDatabase', 3);
request.addEventListener('success', e => {
const db = e.target.result;
const tx = db.transaction('Users','readwrite');
const store = tx.objectStore('Users');
// 刪除資料
const reqDelete = store.delete(1);
reqDelete.addEventListener('success', e => {
console.log('刪除資料成功'); // 李白
})
});
複製程式碼
使用遊標
在上面當中我們使用get()
方法傳入一個主鍵來獲取資料,但是這樣只能夠獲取到一條資料.如果我們想要獲取多條資料了怎麼辦.我們可以使用遊標,來獲取一個區間內的資料.
要使用遊標,我們需要使用物件倉庫上的openCursor()方法建立幣開啟.openCursor()
方法接受兩個引數.
openCursor(range?: IDBKeyRange | number | string | Date | IDBArrayKey, direction?: IDBCursorDirection): IDBRequest;
複製程式碼
第一個是範圍,範圍可以是一個IDBKeyRange
物件.用以下方式建立.
// boundRange 表示主鍵值從1到10(包含1和10)的集合。
// 如果第三個引數為true,則表示不包含最小鍵值1,如果第四引數為true,則表示不包含最大鍵值10,預設都為false
var boundRange = IDBKeyRange.bound(1, 10, false, false);
// onlyRange 表示由一個主鍵值的集合。only() 引數則為主鍵值,整數型別。
var onlyRange = IDBKeyRange.only(1);
// lowerRaneg 表示大於等於1的主鍵值的集合。
// 第二個引數可選,為true則表示不包含最小主鍵1,false則包含,預設為false
var lowerRange = IDBKeyRange.lowerBound(1, false);
// upperRange 表示小於等於10的主鍵值的集合。
// 第二個引數可選,為true則表示不包含最大主鍵10,false則包含,預設為false
var upperRange = IDBKeyRange.upperBound(10, false);
複製程式碼
第二個引數是方向.主要有一下幾種
- next : 遊標中的資料按主鍵值升序排列,主鍵值相等的資料都被讀取
- nextunique : 遊標中的資料按主鍵值升序排列,主鍵值相等只讀取第一條資料
- prev : 遊標中的資料按主鍵值降序排列,主鍵值相等的資料都被讀取
- prevunique : 遊標中的資料按主鍵值降序排列,主鍵值相等只讀取第一條資料
下面讓我們來看一個完整的例子
const request = indexedDB.open('myDatabase', 4);
request.addEventListener('success', e => {
const db = e.target.result;
const tx = db.transaction('Users','readwrite');
const store = tx.objectStore('Users');
const range = IDBKeyRange.bound(1,10);
const req = store.openCursor(range, 'next');
req.addEventListener('success', e => {
const cursor = this.result;
if(cursor){
console.log(cursor.value.userName);
cursor.continue();
}else{
console.log('檢索結束');
}
})
});
複製程式碼
在上面的程式碼中如果檢索到符合條件的資料時,我們可以:
- 使用
cursor.value
拿到資料.- 使用
cursor.updata()
更新資料.- 使用
cursor.delete()
刪除資料.- 使用
cursor.continue()
讀取下一條資料.
索引
在上面程式碼中我們獲取資料都是用的主鍵.但是,在很多情況下我們並不知道我們需要資料的主鍵是什麼,我們知道一個大概的條件.比如說年齡大於20歲的使用者.這個時候我們就需要用到索引.以便有條件的查詢.
建立索引
我們使用物件倉庫的createIndex()方法來建立一個索引.
createIndex(name: string, keyPath: string | string[], optionalParameters?: IDBIndexParameters): IDBIndex;
複製程式碼
createIndex()
方法接收三個引數:
-
第一個引數
name
是索引名,不能重複. -
第二個引數
keyPath
是你要在儲存物件上的那個屬性上建立索引,可以是一個單個的key值,也可以是一個包含key值集合的陣列. -
第三個引數
optionalParameters
是一個可選的物件引數{unique, multiEntry}
- unique: 用來指定索引值是否可以重複,為true代表不能相同,為false時代表可以相同
- multiEntry: 當第二個引數
keyPath
為一個陣列時.如果multiEntry
是true,則會以陣列中的每個元素建立一條索引.如果是false,則以整個陣列為keyPath
值,新增一條索引.
下面讓我們來看一個完整的例子,我們建立一條使用者年齡的索引.
const request = indexedDB.open('myDatabase', 5);
request.addEventListener('upgradeneeded', e => {
const db = e.target.result;
const store = db.createObjectStore('Users', {keyPath: 'userId', autoIncrement: false});
const idx = store.createIndex('ageIndex','age',{unique: false})
});
複製程式碼
這樣我們就建立了一條索引.
使用索引
這在建立了一條索引之後我們就可以來使用它了.我們使用物件倉庫上的index
方法,通過傳入一個索引名.來拿到一個索引物件.
const index = store.index('ageIndex');
複製程式碼
然後我們就可以使用這個索引了.比如說我們要拿到年齡在20歲以上的資料,升序排列.
const request = indexedDB.open('myDatabase', 4);
request.addEventListener('success', e => {
const db = e.target.result;
const tx = db.transaction('Users','readwrite');
const store = tx.objectStore('Users');
const index = store.index('ageIndex');
const req = index.openCursor(IDBKeyRange.lowerBound(20), 'next');
req.addEventListener('success', e => {
const cursor = e.target.result;
if(cursor){
console.log(cursor.value.age);
cursor.continue();
}else{
console.log('檢索結束');
}
})
});
複製程式碼
indexedDB的相容性
上面是我對indexedDB一些粗淺的總結,希望對大家有所幫助.如果文中有何不當之處請予以斧正,謝謝.
參考資料:
我的個人網址: www.wangyiming19950222.com