localStorage 的使用越來越少了?
是時候丟掉 localStorage,擁抱 IndexedDB 了
在 Web 開發的世界里,localStorage 曾是我們的老朋友。它簡單易用,為早期的 Web 應用提供了一種在瀏覽器端持久化存儲少量數據的便捷方式。然而,隨著現代 Web 應用變得越來越復雜,對離線工作和富媒體處理的需求日益增長,localStorage 的局限性也暴露無遺。
今天,我們必須正視一個問題:對于任何嚴肅的 Web 應用項目,繼續依賴 localStorage 已成為一種技術債。是時候丟掉這根“拐杖”,全面擁抱更強大、更專業的 IndexedDB 了。
一、 localStorage 的“罪與罰”:為什么它不再夠用?
我們曾如此喜愛 localStorage 的簡單:
// 存
localStorage.setItem('user', JSON.stringify(user));
// 取
const user = JSON.parse(localStorage.getItem('user'));但正是這種簡單,在現代應用場景下變成了它的“原罪”:
- 極小的存儲容量(約 5MB):在動輒幾 MB 的配置文件、用戶生成內容、緩存數據的今天,5MB 的容量上限如同杯水車薪。一次不小心的
setItem就可能拋出QuotaExceededError異常,導致整個應用崩潰。 - 純字符串的“牢籠”:
localStorage只能存儲字符串。任何復雜的數據結構(如對象、數組、日期、文件)都必須通過JSON.stringify和JSON.parse進行序列化和反序列化。這個過程不僅性能低下,而且會丟失數據類型(例如,日期對象會變成字符串)。 - 阻塞式同步 API:
localStorage的所有操作都是同步的。這意味著當你讀寫一個稍大的數據時,整個 JavaScript 主線程會被阻塞,導致頁面卡頓、無響應,嚴重影響用戶體驗。 - 缺乏結構化查詢能力:你無法對存儲的數據進行復雜的查詢。如果你想從存儲的用戶列表中查找所有年齡大于 18 歲的用戶,你必須將整個列表取出、解析,然后在內存中遍歷——這是一個極其低效的過程。
簡而言之,localStorage 就像一輛小巧的共享單車,適合短途代步(存點 token、用戶偏好),但你絕不能指望它來運送集裝箱(應用狀態、緩存文件、離線數據)。
二、 IndexedDB:為現代 Web 應用而生的數據庫
IndexedDB 不是一個簡單的鍵值存儲,它是一個完整的、事務型的、非關系型數據庫。讓我們來看看它如何完美地解決了 localStorage 的所有痛點:
- 海量存儲空間 瀏覽器通常為每個源提供至少幾十 MB,甚至數百 MB 或更多的存儲空間(取決于用戶磁盤和瀏覽器策略)。這使得存儲大量數據,如圖片、音頻、視頻文件,甚至整個應用的離線包成為可能。
- 存儲原生 JavaScript 對象 IndexedDB 可以直接存儲大多數 JavaScript 對象類型,如對象、數組、
Date、File、Blob等,無需復雜的序列化。這意味著存進去一個Date,取出來還是Date。 - 異步和非阻塞 所有 IndexedDB 操作都是異步的。它們不會阻塞用戶界面,你的應用可以保持流暢的交互,同時在后臺處理龐大的數據讀寫任務。
- 強大的索引和查詢能力 這是 IndexedDB 的核心優勢。你可以為數據對象的不同屬性創建索引,從而實現高性能的搜索、范圍和排序操作。
// 例如,在“用戶”倉庫中,為“age”字段創建索引
const store = transaction.objectStore('users');
const index = store.index('age');
const request = index.getAll(IDBKeyRange.lowerBound(18)); // 獲取所有年齡>=18的用戶5.事務支持 IndexedDB 支持事務,確保一系列數據庫操作要么全部成功,要么全部失敗,維護了數據的完整性和一致性。
三、實戰對比:從“購物車”看差異
假設我們要實現一個功能豐富的購物車。
- 使用 localStorage:
let cart = JSON.parse(localStorage.getItem('cart')) || [];
// 添加一個商品
cart.push(newItem);
// 為了更新,必須重寫整個購物車
localStorage.setItem('cart', JSON.stringify(cart));
// 查找特定商品?需要遍歷整個 cart 數組。
// 數據量大時,每次操作都會引起卡頓。- 使用 IndexedDB:
// 打開數據庫,在事務中操作“cart”倉庫
const transaction = db.transaction(['cart'], 'readwrite');
const store = transaction.objectStore('cart');
// 添加商品,無需重寫整個列表
store.add(newItem);
// 通過商品ID快速查詢
store.get(productId).onsuccess = (event) => {
console.log('找到商品:', event.target.result);
};
// 異步操作,UI 無阻塞四、 擁抱 IndexedDB:從今天開始
誠然,IndexedDB 的 API 以“丑陋”和“復雜”著稱。直接使用原生 API 確實令人望而生畏。但好消息是,我們不必如此。
使用優秀的封裝庫是標準做法。
這些庫極大地簡化了 IndexedDB 的使用,讓你能像操作一個熟悉的 JavaScript 對象或集合一樣與數據庫交互:
- Dexie.js:最受歡迎的 IndexedDB 封裝庫,提供了優雅、簡潔的 API。
// 使用 Dexie,代碼變得清晰易懂
const db = new Dexie('MyAppDB');
db.version(1).stores({
friends: '++id, name, age'
});
await db.friends.add({name: '張三', age: 25});
const youngFriends = await db.friends.where('age').below(18).toArray();- idb:一個輕量級的、基于 Promise 的包裝器,由 Jake Archibald 開發,是底層 API 的良好替代。
- PouchDB:一個兼容 CouchDB 協議的客戶端數據庫,后端可以使用 IndexedDB。

























