前端存儲除了 localStorage 還有啥?
前言
前端的數據存儲方式,你除了用過 Cookies、localStorage 和 sessionStorage 外,還有用過其它的存儲方式么?其實除了前面提到的 3 種存儲方式,目前主流的瀏覽器還支持 Web SQL 和 IndexedDB。
目前市場上主流的瀏覽器有 Chrome 、Safari、Firefox、Opera、UC Browser 和 Internet Explorer 等,其中截止 2020 年 5 月,Chrome 的市場占有率為 「63.93%」,遠遠超過第二名 Safari 瀏覽(「18.19%」)。

這里我們以市場占有率第一的 Chrome 瀏覽器為例,來了解一下它支持的所有存儲方案:

(打開 Chrome 開發者工具,切換至 Application 欄位)
在介紹目前比較流行的一些開源的前端存儲方案之前,阿寶哥先分享一些與存儲有關,有趣好玩的開源庫。
一、有趣好玩的開源庫
1.1 Sharedb
- ❝
- Realtime database backend based on Operational Transformation (OT)。
- https://github.com/share/sharedb
- ❞
ShareDB 是一個基于 JSON 文檔操作轉換(OT)的實時數據庫后端。它是 DerbyJS Web 應用程序框架的實時后端。
「示例1:實時數據同步」

「示例2:展示實時查詢的排行榜應用程序」

1.2 ImmortalDB
- ❝
- A relentless key-value store for the browser。
- https://github.com/gruns/ImmortalDB
- ❞
ImmortalDB 是在瀏覽器中存儲持久鍵值數據的最佳方法。保存到 ImmortalDB 的數據被冗余地存儲在 Cookies,IndexedDB 和 localStorage 中,并且如果其中的任何數據被刪除或損壞,它們將不斷進行自我修復。
例如,清除 Cookie 是一種常見的用戶操作,即使對于非技術用戶也是如此。在存儲壓力下,瀏覽器在沒有警告的情況下隨意刪除 IndexedDB、localStorage 或 sessionStorage。
「示例」
- import { ImmortalDB } from 'immortal-db'
- await ImmortalDB.set('name', 'semlinker'); // Set
- await ImmortalDB.get('name', default='lolo'); // Get
- await ImmortalDB.remove('name'); // Remove
1.3 web-storage-cache
對 localStorage 和 sessionStorage 進行了擴展,添加了超時時間,序列化方法。
https://github.com/wuchangming/web-storage-cache
WebStorageCache 對 HTML5 localStorage 和 sessionStorage 進行了擴展,「添加了超時時間,序列化方法」。可以直接存儲 JSON 對象,同時可以非常簡單的進行超時時間的設置。
優化:WebStorageCache 自動清除訪問的過期數據,避免了過期數據的累積。另外也提供了清除全部過期數據的方法:wsCache.deleteAllExpires();
「示例」
- var wsCache = new WebStorageCache();
- // 緩存字符串'wqteam' 到 'username' 中, 超時時間100秒
- wsCache.set('username', 'wqteam', {exp : 100});
- // 超時截止日期,可用使用Date類型
- var nextYear = new Date();
- nextYear.setFullYear(nextYear.getFullYear() + 1);
- wsCache.set('username', 'wqteam', {exp : nextYear});
- // 獲取緩存中 'username' 的值
- wsCache.get('username');
- // 緩存簡單js對象,默認使用序列化方法為JSON.stringify。
- // 可以通過初始化wsCache的時候配置serializer.serialize
- wsCache.set('user', { name: 'Wu', organization: 'wqteam'});
1.4 lz-string
LZ-based compression algorithm for JavaScript。
https://github.com/pieroxy/lz-string/
lz-string 旨在滿足在 localStorage 中(尤其是在移動設備上)存儲大量數據的需求。localStorage 通常限制為 5MB ~10MB,你可以通過對數據進行壓縮,以存儲更多的數據。
「示例」
- var string = "Hello, my name is semlinker";
- console.log("Size of sample is: " + string.length);
- var compressed = LZString.compress(string);
- console.log("Size of compressed sample is: " + compressed.length);
- string = LZString.decompress(compressed);
- console.log("Sample is: " + string);
下圖是使用官方在線示例進行字符串壓縮測試的結果:

(圖片來源:https://pieroxy.net/blog/pages/lz-string/demo.html)
接下來我們開始來介紹一些主流的數據庫。
二、主流的數據庫
2.1 localForage
Offline storage, improved. Wraps IndexedDB, WebSQL, or localStorage using a simple but powerful API.
https://github.com/localForage/localForage
localForage 是一個快速簡單的 JavaScript 存儲庫。它通過使用類似于 localStorage 的簡單 API 來使用異步存儲(IndexedDB 或 WebSQL)),進而改善你的 Web 應用程序的離線體驗。
對于不支持 IndexedDB 或 WebSQL 的瀏覽器,localForage 會使用 localStorage 進行數據存儲。此外,localForage 還支持存儲所有可以序列化為 JSON 的原生 JS 對象以及 ArrayBuffers,Blob 和 TypedArrays。
localForage 主要支持的平臺:
- IE 10(IE 8+ 使用 localStorage)
- Opera 15(Opera 10.5+ 使用 localStorage)
- Firefox 18
- Safari 3.1(包括 Mobile Safari)
- Chrome 23、Chrome for Android 32
- Phonegap/Apache Cordova 1.2.0
2.2 PouchDB
- PouchDB is a pocket-sized database.
https://github.com/pouchdb/pouchdb
PouchDB 是一個瀏覽器內數據庫,允許應用程序在本地保存數據,以便用戶即使在離線時也可以享受應用程序的所有功能。另外,數據在客戶端之間是同步的,因此用戶可以隨時隨地保持最新狀態。
PouchDB 也在 Node.js 中運行,可以用作與 「CouchDB」 兼容的服務器的直接接口。該 API 在每個環境中工作都是相同的,因此你可以花更少的時間來擔心瀏覽器的差異,而花更多的時間來編寫干凈、一致的代碼。
PouchDB 支持所有現代瀏覽器:
- Firefox 29+ (Including Firefox OS and Firefox for Android)
- Chrome 30+
- Safari 5+
- Internet Explorer 10+
- Opera 21+
- Android 4.0+
- iOS 7.1+
- Windows Phone 8+
PouchDB 在幕后使用 IndexedDB,若當前環境不支持 IndexedDB 則回退到 Web SQL。
2.3 Rxdb
A realtime Database for JavaScript Applications.
https://github.com/pubkey/rxdb
RxDB(Reactive Database 的縮寫)是 NoSQL 數據庫,用于 JavaScript 應用程序,如網站,混合應用程序,Electron Apps,Progressive Web Apps 和 Node.js。響應式意味著你不僅可以查詢當前狀態,還可以訂閱所有狀態更改,比如查詢的結果或文檔的單個字段。

這對于基于 UI 的實時應用程序非常有用,因為它易于開發,并且具有很大的性能優勢。為了在客戶端和服務器之間復制數據,RxDB 提供了用于與任何 CouchDB 兼容端點以及自定義 GraphQL 端點進行實時復制的模塊。
RxDB 支持以下特性:
- Mango-Query:支持 mquery API 從集合中獲取數據,支持鏈式的 mongoDB 查詢風格。
- Replication:因為 RxDB 依賴于 PouchDB,因此很容易實現終端設備與服務器之間的數據同步。
- Reactive:RxDB 使得同步 DOM 的狀態變得很簡單。
- MultiWindow/Tab:當 RxDB 的兩個實例使用相同的存儲引擎,它們的狀態和操作流將會被廣播。這意味著對于兩個瀏覽器窗口,窗口 #1 的數據變化也會自動影響窗口 #2 的數據狀態。
- Schema:通過 jsonschema 來定義 Schemas,它們用來描述數據格式。
- Encryption:通過將模式字段設置為encrypted,該字段的值將以加密模式存儲,沒有密碼就無法讀取。
2.4 NeDB
The JavaScript Database, for Node.js, nw.js, electron and the browser.
https://github.com/louischatriot/nedb
NeDB 是一個 JavaScript 數據庫,能夠運行在 Node.js、nw.js、Electron 和瀏覽器環境。它是使用純的 JavaScript 實現,不依賴其它庫,提供的 API 是 MongoDB API 的子集,重要的是它的速度非常快:
- 插入:10,680 ops/s
- 查找:43,290 ops/s
- 更新:8,000 ops/s。
- 刪除:11,750 ops/s。
ops (operation per second) 即表示每秒操作的次數。
2.5 Dexie.js
A Minimalistic Wrapper for IndexedDB.
https://github.com/dfahlander/Dexie.js
Dexie.js 是 IndexedDB 的包裝庫,它提供了一套經過精心設計的 API,強大的錯誤處理,較強的可擴展性,此外它能夠跟蹤數據變化,支持 KeyRange (搜索不區分大小寫,可設置匹方式和 OR 操作)。
Dexie.js 主要為了解決原生 IndexedDB API 中存在的三個主要問題:
- 異常錯誤處理。
- 較弱的查詢功能。
- 代碼復雜性。
為了便于開發者接入 Dexie.js,在 Dexie.js 官網中提供了豐富的示例:
- React + Dexie
- React + Redux + Dexie
- Dexie with Typescript
- Angular + Dexie
- Dexie with Electron
- Full Text Search
以上只列出部分示例,了解更多示例請訪問:Dexie.js - Samples(
https://dexie.org/docs/Samples)。最后我們來簡單介紹一下各種 Web 存儲方案。
三、各種 Web 存儲方案簡介
3.1 Cookie
HTTP Cookie(也叫 Web Cookie 或瀏覽器 Cookie)是服務器發送到用戶瀏覽器并保存在本地的一小塊數據,它會在瀏覽器下次向同一服務器再發起請求時被攜帶并發送到服務器上。通常,它用于告知服務端兩個請求是否來自同一瀏覽器,如保持用戶的登錄狀態。
Cookie 主要用于以下三個方面:
- 會話狀態管理(如用戶登錄狀態、購物車、游戲分數或其它需要記錄的信息);
- 個性化設置(如用戶自定義設置、主題等);
- 瀏覽器行為跟蹤(如跟蹤分析用戶行為等)。
Cookie 的特點:
- Cookie 的大小受限,一般為 4 KB;
- 同一個域名下存放 Cookie 的個數是有限制的,不同瀏覽器的個數不一樣,一般為 20 個;
- Cookie 支持設置過期時間,當過期時自動銷毀;
- 每次發起同域下的 HTTP 請求時,都會攜帶當前域名下的 Cookie;
- 支持設置為 HttpOnly,防止 Cookie 被客戶端的 JavaScript 訪問。
「示例1:簡單用法」
- document.cookie = "name=semlinker";
- document.cookie = "favorite_food=tripe";
- alert(document.cookie);
- // 顯示: name=semlinker;favorite_food=tripe
「示例2:得到名為 test2 的 cookie」
- document.cookie = "test1=Hello";
- document.cookie = "test2=World";
- var myCookie = document.cookie
- .replace(/(?:(?:^|.*;\s*)test2\s*\=\s*([^;]*).*$)|^.*$/, "$1");
- alert(myCookie);
3.2 localStorage
一種持久化的存儲方式,也就是說如果不手動清除,數據就永遠不會過期。它是采用鍵值對的方式存儲數據,按域名將數據分別保存到對應數據庫文件里。相比 Cookie 來說,它能保存更大的數據。
localStorage 的特點:
- 大小限制為 5MB ~10MB;
- 在同源的所有標簽頁和窗口之間共享數據;
- 數據僅保存在客戶端,不與服務器進行通信;
- 數據持久存在且不會過期,重啟瀏覽器后仍然存在;
- 對數據的操作是同步的。
「示例」
- // 通過setItem()增加一個數據項
- localStorage.setItem('myName', 'Semlinker');
- // 通過getItem()獲取某個數據項
- let me = localStorage.getItem('myName');
- // 通過removeItem()移除某個數據項
- localStorage.removeItem('myName');
- // 移除所有數據項
- localStorage.clear();
3.3 sessionStorage
與服務端的 session 類似,sessionStorage 是一種會話級別的緩存,關閉瀏覽器時數據會被清除。需要注意的是 sessionStorage 的作用域是窗口級別的,也就是說不同窗口之間保存的 sessionStorage 數據是不能共享的。
- sessionStorage 的特點:
- sessionStorage 的數據只存在于當前瀏覽器的標簽頁;
- 數據在頁面刷新后依然存在,但在關閉瀏覽器標簽頁之后數據就會被清除;
- 與 localStorage 擁有統一的 API 接口;
- 對數據的操作是同步的。
「示例」
- // 通過setItem()增加一個數據項
- sessionStorage.setItem('myName', 'Semlinker');
- // 通過getItem()獲取某個數據項
- let me = sessionStorage.getItem('myName');
- // 通過removeItem()移除某個數據項
- sessionStorage.removeItem('myName');
- // 移除所有數據項
- sessionStorage.clear();
3.4 Web SQL
Web SQL 數據庫 API 實際上不是 HTML5 規范的一部分,而是一個單獨的規范,它引入了一組 API 來使用 SQL 來操作客戶端數據庫。需要注意的是,HTML5 已經放棄 Web SQL 數據庫。
Web SQL Database 規范中定義的三個核心方法:
- openDatabase:這個方法使用現有數據庫或新建數據庫來創建數據庫對象;
- transaction:這個方法允許我們根據情況控制事務的提交或回滾;
- executeSql:這個方法用于執行真實的 SQL 語句。
Web SQL 的特點(相比 Cookie、localStorage 與 sessionStorage):
- Web SQL 能方便進行對象存儲;
- Web SQL 支持事務,能方便地進行數據查詢和數據處理操作。
「示例」
- var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);
- db.transaction(function (tx) {
- // 執行查詢操作
- tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)');
- // 執行插入操作
- tx.executeSql('INSERT INTO LOGS (id, log) VALUES (1, "foobar")');
- tx.executeSql('INSERT INTO LOGS (id, log) VALUES (2, "logmsg")');
- });
3.5 IndexedDB
IndexedDB 是一種底層 API,用于客戶端存儲大量結構化數據,包括文件、二進制大型對象。該 API 使用索引來實現對該數據的高性能搜索。雖然 Web Storage 對于存儲較少量的數據很有用,但對于存儲更大量的結構化數據來說,這種方法不太好用。IndexedDB 提供了一個解決方案。
IndexedDB 的特點:
- 存儲空間大:存儲空間可以達到幾百兆甚至更多;
- 支持二進制存儲:它不僅可以存儲字符串,而且還可以存儲二進制數據;
- IndexedDB 有同源限制,每一個數據庫只能在自身域名下能訪問,不能跨域名訪問;
- 支持事務型:IndexedDB 執行的操作會按照事務來分組的,在一個事務中,要么所有的操作都成功,要么所有的操作都失敗;
- 鍵值對存儲:IndexedDB 內部采用對象倉庫(object store)存放數據。所有類型的數據都可以直接存入,包括 JavaScript 對象。對象倉庫中,數據以 “鍵值對” 的形式保存,每一個數據記錄都有對應的主鍵,主鍵是獨一無二的,不能有重復,否則會拋出一個錯誤。
- 數據操作是異步的:使用 IndexedDB 執行的操作是異步執行的,以免阻塞應用程序。
「示例」
- var dbName = "my_db";
- var request = indexedDB.open(dbName, 2);
- request.onerror = function(event) {
- // 錯誤處理
- };
- request.onupgradeneeded = function(event) {
- var db = event.target.result;
- // 建立一個對象倉庫來存儲我們客戶的相關信息,我們選擇 ssn 作為鍵路徑(key path)
- // 因為 ssn 可以保證是不重復的
- var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });
- // 建立一個索引來通過姓名來搜索客戶。名字可能會重復,所以我們不能使用 unique 索引
- objectStore.createIndex("name", "name", { unique: false });
- // 使用郵箱建立索引,我們確保客戶的郵箱不會重復,所以我們使用 unique 索引
- objectStore.createIndex("email", "email", { unique: true });
- // 使用事務的 oncomplete 事件確保在插入數據前對象倉庫已經創建完畢
- objectStore.transaction.oncomplete = function(event) {
- // 將數據保存到新創建的對象倉庫
- var customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers");
- customerData.forEach(function(customer) {
- customerObjectStore.add(customer);
- });
- };
- };
篇幅有限這里我們只介紹了部分開源庫,其實還有一些其它成熟的開源庫,比如 lowdb(Local JSON Database)、Lovefield(Relational Database)和 LokiJS(NoSQL Database)等