前言
在項(xiàng)目開發(fā)過程中,前端需要存儲大量的數(shù)據(jù)。cookie, localstorage 都有存儲長度限制。
表格一覽
特性 | cookie | localStorage | sessionStorage | indexedDB |
---|---|---|---|---|
數(shù)據(jù)生命周期 | 一般由服務(wù)器生成,可以設(shè)置過期時(shí)間;前端采用和js-cookie等組件也可以生成 | 除非被清理,否則一直存在;瀏覽器關(guān)閉還會保存在本地,但是不支持跨瀏覽器 | 頁面關(guān)閉就清理刷新依然存在,不支持跨頁面交互 | 除非被清理,否則一直存在 |
數(shù)據(jù)存儲大小 | 4K | 5M | 5M | 不限制大小 |
與服務(wù)端通信 | 每次都會攜帶在請求的header 中,對于請求性能有影響;同時(shí)由于請求中都帶有,所以也容易出現(xiàn)安全問題 | 不參與 | 不參與 | 不參與 |
特點(diǎn) | 字符串鍵值對在本地存儲數(shù)據(jù) | 字符串鍵值對在本地存儲數(shù)據(jù) | 字符串鍵值對在本地存儲數(shù)據(jù) | IndexedDB 是一個(gè)非關(guān)系型數(shù)據(jù)庫(不支持通過 SQL 語句操作)。可以存儲大量數(shù)據(jù),提供接口來查詢,還可以建立索引,這些都是其他存儲方案無法提供的能力。 |
需要一個(gè)存儲容量大,支持搜索和自定義索引的前端存儲方案,就選用了 。
caniuse上查看 indexedDB 支持情況,目前瀏覽器支持情況良好。
caniuse.com/?search=ind…
IndexedDB介紹
IndexedDB 屬于非關(guān)系型數(shù)據(jù)庫。(不支持SQL查詢)
特點(diǎn):
- 鍵值對儲存 IndexedDB 內(nèi)部采用對象倉庫(object store)存放數(shù)據(jù)。所有類型的數(shù)據(jù)都可以直接存入,包括 JAVAScript 對象。對象倉庫中,數(shù)據(jù)以"鍵值對"的形式保存,每一個(gè)數(shù)據(jù)記錄都有對應(yīng)的主鍵,主鍵是獨(dú)一無二的,不能有重復(fù),否則會拋出一個(gè)錯(cuò)誤。
- 異步 IndexedDB 操作時(shí)不會鎖死瀏覽器,用戶依然可以進(jìn)行其他操作,這與 LocalStorage 形成對比,后者的操作是同步的。異步設(shè)計(jì)是為了防止大量數(shù)據(jù)的讀寫,拖慢網(wǎng)頁的表現(xiàn)。
- 支持事務(wù) IndexedDB 支持事務(wù)(transaction),這意味著一系列操作步驟之中,只要有一步失敗,整個(gè)事務(wù)就都取消,數(shù)據(jù)庫回滾到事務(wù)發(fā)生之前的狀態(tài),不存在只改寫一部分?jǐn)?shù)據(jù)的情況。
- 同源限制 IndexedDB 受到同源限制,每一個(gè)數(shù)據(jù)庫對應(yīng)創(chuàng)建它的域名。網(wǎng)頁只能訪問自身域名下的數(shù)據(jù)庫,而不能訪問跨域的數(shù)據(jù)庫。
- 支持二進(jìn)制儲存 IndexedDB 不僅可以儲存字符串,還可以儲存二進(jìn)制數(shù)據(jù)(ArrayBuffer 對象和 Blob 對象。
- 儲存空間大 IndexedDB 的儲存空間比 LocalStorage 大得多,一般來說不少于 250MB,甚至沒有上限。儲 存 在 電 腦 上 中 的 位 置 為 C:Users當(dāng) 前 的 登 錄 用 戶AppDataLocalgoogleChromeUser DataDefaultIndexedDB
核心概念
- 數(shù)據(jù)庫:IDBDatabase 對象,數(shù)據(jù)庫有版本概念,同一時(shí)刻只能有一個(gè)版本,每個(gè)域名可以建多個(gè)數(shù)據(jù)庫
- 對象倉庫:IDBObjectStore 對象,類似于關(guān)系型數(shù)據(jù)庫的表格
- 索引: IDBIndex 對象,可以在對象倉庫中,為不同的屬性建立索引,主鍵建立默認(rèn)索引
- 事務(wù): IDBTransaction 對象,增刪改查都需要通過事務(wù)來完成,事務(wù)對象提供了error,abord,complete三個(gè)回調(diào)方法,監(jiān)聽操作結(jié)果
- 操作請求:IDBRequest 對象
- 指針: IDBCursor 對象
- 主鍵集合:IDBKeyRange 對象,主鍵是默認(rèn)建立索引的屬性,可以取當(dāng)前層級的某個(gè)屬性,也可以指定下一層對象的屬性,還可以是一個(gè)遞增的整數(shù)
indexedDB使用
基礎(chǔ)操作
1. 創(chuàng)建數(shù)據(jù)庫 & 新建表和索引
typescript
復(fù)制代碼
/* *@databaseName 數(shù)據(jù)倉庫的名字 *@version 數(shù)據(jù)倉庫的版本 *@databaseName 數(shù)據(jù)倉庫的名字 */ var request = window.indexedDB.open('group', 1); /* *數(shù)據(jù)倉庫打開失敗 */ request.onerror = function(error) { console.log('IndexedDB 打開失敗', error); }; /* *數(shù)據(jù)倉庫打開成功 */ request.onsuccess = function(res) { console.log('IndexedDB 打開成功', res); db = res.target.result; }; /* *數(shù)據(jù)倉庫升級事件(第一次新建庫是也會觸發(fā),因?yàn)閿?shù)據(jù)倉庫從無到有算是升級了一次) */ request.onupgradeneeded = function(res) { console.log('IndexedDB 升級成功', res); db = res.target.result; db_table = db.createObjectStore('group', { keyPath: 'id' }); db_table.createIndex('indexName', 'name', { unique: false }); };
2. 新增數(shù)據(jù)
typescript
復(fù)制代碼
/* *新建事務(wù) *@params 數(shù)據(jù)倉庫的數(shù)組 *@params 寫入模式 */ var store = db.transaction(['group'], 'readwrite').objectStore('group'); /* *add方法添加數(shù)據(jù) *@params 需要添加的數(shù)據(jù)信息 */ var request = store.add({ id: new Date().getTime(), name: '王二', age: 12, emAIl: '[email protected]', }); /* *添加成功 */ request.onsuccess = function(event) { console.log('數(shù)據(jù)添加成功', event); }; /* *添加失敗 */ request.onerror = function(event) { console.log('數(shù)據(jù)添加失敗', event); };
3. 讀取數(shù)據(jù)
typescript
復(fù)制代碼
/* *新建事務(wù) *@params 數(shù)據(jù)倉庫的數(shù)組 */ var store = db.transaction(['group']).objectStore('group'); /* *get方法獲取數(shù)據(jù) *@params 數(shù)據(jù)的主鍵 */ var request = store.get(1678664831491); /* *獲取成功 */ request.onsuccess = function(event) { if (event.target.result) { console.log('數(shù)據(jù)獲取成功', event.target.result); } else { console.log('未獲取到數(shù)據(jù)'); } }; /* *獲取失敗 */ request.onerror = function(event) { console.log('數(shù)據(jù)獲取失敗', event); };
4. 更新數(shù)據(jù)
typescript
復(fù)制代碼
/* *新建事務(wù) *@params 數(shù)據(jù)倉庫的數(shù)組 *@params 寫入模式 */ var store = db.transaction(['group'], 'readwrite').objectStore('group'); /* *put方法根據(jù)主鍵更新數(shù)據(jù) *@params 數(shù)據(jù)的主鍵 */ var request = store.put({ id: 1678664831491, name: '張一' + Math.random(), age: 24, email: '[email protected]', }); /* *更新成功 */ request.onsuccess = function(event) { console.log('數(shù)據(jù)更新成功', event); }; /* *更新失敗 */ request.onerror = function(event) { console.log('數(shù)據(jù)更新失敗', event); };
未加 readwrite, 會拋錯(cuò),修改數(shù)據(jù)失敗
5. 刪除數(shù)據(jù)
typescript
復(fù)制代碼
/* *新建事務(wù) *@params 數(shù)據(jù)倉庫的數(shù)組 */ var store = db.transaction(['group'], 'readwrite').objectStore('group'); /* *delete方法刪除數(shù)據(jù) *@params 數(shù)據(jù)的主鍵 */ var request = store.delete(1678664831491); /* *刪除成功 */ request.onsuccess = function (event) { console.log('數(shù)據(jù)刪除成功',event); }; /* *刪除失敗 */ request.onerror = function (event) { console.log('數(shù)據(jù)刪除失敗',event); };
6. 使用索引
typescript
復(fù)制代碼
/* *新建事務(wù) *@params 數(shù)據(jù)倉庫的數(shù)組 */ var store = db.transaction(['group']).objectStore('group'); /* *index方法獲取索引對象 *get方法獲取數(shù)據(jù) *@params 數(shù)據(jù)的索引 */ var request = store.index('indexName').get('張四'); /* *獲取成功 */ request.onsuccess = function (event) { console.log('通過索引獲取數(shù)據(jù)成功',event.target.result); }; /* *獲取失敗 */ request.onerror = function (event) { console.log('通過索引獲取數(shù)據(jù)失敗',event); };
7. 獲取整張表所有的data
typescript
復(fù)制代碼
var store = db.transaction(['group']).objectStore('group'); var request = store.getAll(); /* *更新成功 */ request.onsuccess = function(event) { console.log('indexedDB getAll:', event.target.result); }; /* *更新失敗 */ request.onerror = function(event) { console.log('indexedDB getAll:', event); };
8. 根據(jù)指定條件獲取data
首先讓我們 來了解 IDBKeyRange
的API
www.w3cschool.cn/javascript_…
typescript
復(fù)制代碼
var store = db.transaction(['group']).objectStore('group'); // 獲取id名稱小于當(dāng)前時(shí)間的所有data var request = store.getAll(IDBKeyRange.upperBound(+new Date())); /* *更新成功 */ request.onsuccess = function(event) { console.log('indexedDB getAll:', event.target.result); }; /* *更新失敗 */ request.onerror = function(event) { console.log('indexedDB getAll:', event); };
業(yè)務(wù)中優(yōu)雅使用
indexedDB 并非無底洞,可以無限存儲。要考慮做定期刪除等功能
1. 定期刪除失效數(shù)據(jù)
1. 首先我們創(chuàng)建數(shù)據(jù)的時(shí)候就以時(shí)間戳+失效時(shí)間來約定id規(guī)則
2. 再通過上面基礎(chǔ)操作的getAll
方法,獲取指定條件的data
,再遍歷data
,調(diào)用刪除數(shù)據(jù)API
JavaScript
復(fù)制代碼
var store = db.transaction(['group'], 'readwrite').objectStore('group'); var request = store.getAll(IDBKeyRange.upperBound(+new Date())); /* *更新成功 */ request.onsuccess = function(event) { console.log('indexedDB getAll:', event); console.log('indexedDB getAll:', event.target.result); const data = event.target.result; data.forEach(item => { console.log('刪除數(shù)據(jù)', item); const deletRequest = store.delete(item.id); /* *刪除成功 */ deletRequest.onsuccess = function(event) { console.log('數(shù)據(jù)刪除成功', event); }; /* *刪除失敗 */ deletRequest.onerror = function(event) { console.log('數(shù)據(jù)刪除失敗', event); }; }); }; /* *更新失敗 */ request.onerror = function(event) {};
3. 我們把上述方法包裝下每次打開頁面,清空下失效數(shù)據(jù)。就可以實(shí)現(xiàn)一個(gè)定期刪除失效數(shù)據(jù)的方法啦
2. 批量添加數(shù)據(jù)
typescript
復(fù)制代碼
const TestData = [ { event: 'NE-TEST1', level: 'warning', errorCode: 200, url: 'http://www.example.com', time: '2017/11/8 下午4:53:039', isUploaded: false }, { event: 'NE-TEST2', msg: '測試2', level: 'error', errorCode: 1000, url: 'http://www.example.com', time: '2017/11/8 下午4:53:042', isUploaded: false }, { event: 'NE-TEST3', msg: '測試3', level: 'info', errorCode: 3000, url: 'http://www.example.com', time: '2017/11/8 下午4:53:043', isUploaded: false }, { event: 'NE-TEST4', mgs: '測試4', level: 'info', url: 'http://www.example.com', time: '2017/11/8 下午4:53:0423', isUploaded: false } ] /** * 添加數(shù)據(jù) * @param {array} docs 要添加數(shù)據(jù) * @param {string} objName 倉庫名稱 */ function addData (docs, objName) { if (!(docs && docs.length)) { throw new Error('docs must be a array!') } return openIndexedDB().then(db => { const tx = db.transaction([objName], 'readwrite') tx.oncomplete = e => { console.log('tx:addData onsuccess', e) return Promise.resolve(docs) } tx.onerror = e => { e.stopPropagation() console.error('tx:addData onerror', e.target.error) return Promise.reject(e.target.error) } tx.onabort = e => { console.warn('tx:addData abort', e.target) return Promise.reject(e.target.error) } const obj = tx.objectStore(objName) docs.forEach(doc => { const req = obj.add(doc) /** * NOTE: * request * 兩個(gè)事件: * 1. success * 2. error */ // req.onsuccess = e => console.log('obj:addData onsuccess', e.target) req.onerror = e => { console.error('obj:addData onerror', e.target.error) } }) }) } addData(TestData, OB_NAMES.UseKeyGenerator) .then(() => addData(TestData, OB_NAMES.UseKeyPath))
作者:叫叫技術(shù)團(tuán)隊(duì)
鏈接:https://juejin.cn/post/7239259798267904059
來源:稀土掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。