前端最能打的本地存儲方案
來源:網(wǎng)絡(luò)
前言
之前開發(fā)了一個離線存儲的需求,需要在本地存儲較大的數(shù)據(jù)量,并且還要考慮到多種場景下的存儲方式兼容。 產(chǎn)品的原話就是“要又大又全”。 既然存儲量大,也要覆蓋全多種設(shè)備多種瀏覽器。
方案選擇
- 既然要存儲的數(shù)量大,得排除cookie
- localStorage,雖然比cookie多,但是同樣有上限(5M)左右,備選
- websql 使用簡單,存儲量大,兼容性差,備選
- indexDB api多且繁瑣,存儲量大、高版本瀏覽器兼容性較好,備選
既然羅列了一些選擇,都沒有十全十美的,那么有沒有一種能夠集合這多種方式的插件呢?漸進(jìn)增強 or 優(yōu)雅降級 的存在
沖著這個想法,就去github和谷歌找了一下,還真的有這么一個插件。
那就是 localforage
localforage
localForage 是一個 JavaScript 庫,只需要通過簡單類似 localStorage API 的異步存儲來改進(jìn)你的 Web 應(yīng)用程序的離線體驗。它能存儲多種類型的數(shù)據(jù),而不僅僅是字符串。
關(guān)于兼容性
localForage 有一個優(yōu)雅降級策略,若瀏覽器不支持 IndexedDB 或 WebSQL,則使用 localStorage。在所有主流瀏覽器中都可用:Chrome,F(xiàn)irefox,IE 和 Safari(包括 Safari Mobile)。下面是 indexDB、web sql、localStorage 的一個瀏覽器支持情況,可以發(fā)現(xiàn),兼容性方面loaclForage基本上滿足99%需求
關(guān)于存儲量
首先indexDB的存儲,理論上是硬件有多大內(nèi)存就可以存多少,但是有些瀏覽器廠商會限制,具體限制各家不同,但是基本最小是250M起步
使用
解決了兼容性和存儲量的點,我們就來看看localforage的基礎(chǔ)用法
安裝
# 通過 npm 安裝:
npm install localforage
// 直接引用
<script src="localforage.js"></script>
<script>console.log('localforage is: ', localforage);</script>
獲取存儲
getItem(key, successCallback)
從倉庫中獲取 key 對應(yīng)的值并將結(jié)果提供給回調(diào)函數(shù)。如果 key 不存在,getItem() 將返回 null。
localforage.getItem('somekey').then(function(value) {
// 當(dāng)離線倉庫中的值被載入時,此處代碼運行
console.log(value);
}).catch(function(err) {
// 當(dāng)出錯時,此處代碼運行
console.log(err);
});
// 回調(diào)版本:
localforage.getItem('somekey', function(err, value) {
// 當(dāng)離線倉庫中的值被載入時,此處代碼運行
console.log(value);
});
設(shè)置存儲
setItem(key, value, successCallback)
將數(shù)據(jù)保存到離線倉庫。你可以存儲如下類型的 JavaScript 對象:
-
Array -
ArrayBuffer -
Blob -
Float32Array -
Float64Array -
Int8Array -
Int16Array -
Int32Array -
Number -
Object -
Uint8Array -
Uint8ClampedArray -
Uint16Array -
Uint32Array -
String
localforage
.setItem("somekey", "some value")
.then(function (value) {
// 當(dāng)值被存儲后,可執(zhí)行其他操作
console.log(value);
})
.catch(function (err) {
// 當(dāng)出錯時,此處代碼運行
console.log(err);
});
// 不同于 localStorage,你可以存儲非字符串類型
localforage
.setItem("my array", [1, 2, "three"])
.then(function (value) {
// 如下輸出 `1`
console.log(value[0]);
})
.catch(function (err) {
// 當(dāng)出錯時,此處代碼運行
console.log(err);
});
// 你甚至可以存儲 AJAX 響應(yīng)返回的二進(jìn)制數(shù)據(jù)
req = new XMLHttpRequest();
req.open("GET", "/photo.jpg", true);
req.responseType = "arraybuffer";
req.addEventListener("readystatechange", function () {
if (req.readyState === 4) {
// readyState 完成
localforage
.setItem("photo", req.response)
.then(function (image) {
// 如下為一個合法的 <img> 標(biāo)簽的 blob URI
var blob = new Blob([image]);
var imageURI = window.URL.createObjectURL(blob);
})
.catch(function (err) {
// 當(dāng)出錯時,此處代碼運行
console.log(err);
});
}
});
刪除存儲
removeItem(key, successCallback)
從離線倉庫中刪除 key 對應(yīng)的值。
localforage.removeItem('somekey').then(function() {
// 當(dāng)值被移除后,此處代碼運行
console.log('Key is cleared!');
}).catch(function(err) {
// 當(dāng)出錯時,此處代碼運行
console.log(err);
});
清空存儲
clear(successCallback)
從數(shù)據(jù)庫中刪除所有的 key,重置數(shù)據(jù)庫。
localforage.clear() 將會刪除離線倉庫中的所有值。謹(jǐn)慎使用此方法。
localforage.clear().then(function() {
// 當(dāng)數(shù)據(jù)庫被全部刪除后,此處代碼運行
console.log('Database is now empty.');
}).catch(function(err) {
// 當(dāng)出錯時,此處代碼運行
console.log(err);
});
更多
除了基本的增刪查改,還有一些配置,如指定具體使用哪一種存儲方式、設(shè)置數(shù)據(jù)庫的名稱、長度等信息 可參考 官方文檔
localforage是否萬事大吉?
用上了localforage一開始我也以為可以完全滿足萬惡的產(chǎn)品了,然而。。。翻車了
問題
在這個功能上線半年,一直相安無事,有一天晚上突然產(chǎn)品說接到反饋說有用戶的手機(jī)進(jìn)入頁面沒有緩存上次的操作數(shù)據(jù)。
我第一反應(yīng),“不可能,絕對不可能”
我詢問了一下,用戶的手機(jī)是什么型號,當(dāng)我看到手機(jī)圖片的時候。。。我是沒想到。。。
如下圖:
這玩意,一些小年輕都可能沒見過。。。。iphone4哇,現(xiàn)在是出到了iphone14了吧???
不得了不得了,iphone4居然也是我們的用戶群體???
分析
既然遇上了,還是冷靜分析一下吧。起初第一反應(yīng)是這古董機(jī)的兼容性有問題,是不是只支持localstorage導(dǎo)致只能存儲5M的內(nèi)容,超過了上限,導(dǎo)致無法緩存了?
然而,當(dāng)產(chǎn)品不知道從哪找到了一部iphone4給我(我也真的服了這個老6),我拿到真機(jī)試了下,得到讓我無法呼吸的結(jié)果,iphone4這古董機(jī)居然支持indexDB,那么就不是超過了5M的上限導(dǎo)致緩存失敗了
進(jìn)一步假設(shè)
在知道iphone4居然支持indexDB后,我失去頭緒了,拿著十年前的這個古董機(jī),隨便翻翻,看看系統(tǒng),看看版本,沒看出什么問題,但是我發(fā)現(xiàn)這iphone4的內(nèi)存也是出奇的小,只有8G內(nèi)存。等等,8G內(nèi)存,如果手機(jī)內(nèi)存不足的前提下,localforage繼續(xù)緩存會怎么樣?
隨即,隨便下載點軟件,毫不費力就將這臺iphone4的內(nèi)存整得只剩下50M不到了,手機(jī)已經(jīng)開始提示要清理內(nèi)存。
在這種狀態(tài)下,嘗試使用localforage,不出意外,拋錯了 QuotaExceededError 的 DOMError
延伸
雖然現(xiàn)在的硬件設(shè)備內(nèi)存大部分都很大,但是本著產(chǎn)品的“又大又全”理念,還是打算處理一下。當(dāng)然除了處理這臺古董機(jī),也延伸出更多優(yōu)化的可能性
- 當(dāng)設(shè)備不支持 indexDB和web sql的時候,只支持loaclStorage存儲量只有5M,應(yīng)該怎么處理?
- 如果存儲數(shù)據(jù)出現(xiàn)了臟數(shù)據(jù)或者讀取問題,想要清理用戶設(shè)備上的數(shù)據(jù)怎么處理?
解決
存儲數(shù)據(jù)的時候加上存儲的時間戳和模塊標(biāo)識,加時間戳一起存儲
setItem({
value: '1',
label: 'a',
module: 'a',
timestamp: '11111111111'
})
- 如果是遇到存儲使用報錯的情況,try/catch捕獲之后,通過判斷報錯提示,去執(zhí)行相應(yīng)的操作,遇到內(nèi)存不足的情況,則根據(jù)時間戳和模塊標(biāo)識清理一部分舊數(shù)據(jù)(內(nèi)存不足的情況還是比較少的)
- 在用戶手機(jī)上產(chǎn)生臟數(shù)據(jù)的情況,想要清理的這種情況的 處理方式是:
- 讓后端在用戶信息接口里面加上緩存有效期時間戳,當(dāng)該時間戳存在,則前端會進(jìn)行一次對本地存儲掃描
- 在有效期時間戳之前的數(shù)據(jù),結(jié)合模塊標(biāo)識,進(jìn)行清理,清理完畢后調(diào)用后端接口上報清理日志
- 模塊標(biāo)識的意義是清理數(shù)據(jù)的時候,可以按照模塊去清理(選填)
推薦閱讀 點擊標(biāo)題可跳轉(zhuǎn)
1、JS 中變量存儲在堆中還是棧中?(深入內(nèi)存原理)
