有沒有遇過這些需求:希望筆記離線也能開、想把大量資料快取下來讓頁面秒開、希望圖片能快取而不是每次都打 API、甚至在前端就能做搜尋或分類?這時候,瀏覽器內建的重量級儲存工具 IndexedDB 就派上用場了。📦
前端常見的本地儲存方式,可以依照容量、使用場景與限制來比較:
所以,大型資料快取、離線可用、前端搜尋/分類,IndexedDB 才是最合適的選擇。
IndexedDB 就像瀏覽器內建的 NoSQL 資料庫:
和 SQL DB 不同的是,你不需要先定義 schema,再 insert 一大堆 row;你可以直接把 JS 物件塞進去,瀏覽器會幫你管理。
indexedDB.open(name, version)
name
:資料庫名稱。version
:版本號,schema 變動要升級版本。IDBDatabase
db
物件。db.createObjectStore(name, options)
keyPath
:指定物件裡哪個欄位是主鍵。autoIncrement
:若沒提供主鍵,瀏覽器自動生成遞增 id。IDBRequest
request
。request.onsuccess
、request.onerror
:透過事件監聽結果。// 建立名為 "MyDB"、版本 1 的資料庫。
const request = indexedDB.open("MyDB", 1);
// 只會在:第一次建立資料庫、或你把版本號從舊版升到新版時觸發。
request.onupgradeneeded = (event) => {
// 取得資料庫實例(IDBDatabase)
const db = event.target.result;
// 只有在「還沒有這個 Object Store」時才建立,避免重複錯誤
if (!db.objectStoreNames.contains("notes")) {
// 建立一個名為 "notes" 的 Object Store(類似資料表)
// keyPath: 使用物件的 "id" 欄位當主鍵
// autoIncrement: 若未提供 id,瀏覽器自動遞增產生主鍵
db.createObjectStore("notes", { keyPath: "id", autoIncrement: true });
}
};
// 成功打開資料庫(且完成可能的升級後)
// 後續所有 CRUD 都要用這裡拿到的 db 來建立 transaction
request.onsuccess = (event) => {
const db = event.target.result;
console.log("✅ 資料庫打開成功", db);
};
request.onerror = (event) => {
console.error("❌ 打開失敗", event.target.error);
};
在 IndexedDB 裡,所有操作都必須透過 Transaction(交易)。
就像在銀行裡,你要辦理存款、提款,一定要先「抽號碼牌 → 進入櫃台」;而 Transaction 就是這個「櫃台流程」。
db.transaction(storeNames, mode)
readonly
:只讀,一般 R 都會用這個。readwrite
:可讀寫,一般 CUD 都會用這個。versionchange
:只有在升級 schema(onupgradeneeded)時才會用到。Transaction 擁有資料庫常見的 ACID 四大保證:
readwrite
交易:同一個 Object Store 不會同時有兩個寫操作在跑,避免衝突。readonly
交易可以並行,但看不到尚未提交的寫入。transaction.oncomplete
事件觸發時,表示資料已經真正寫入本地儲存,不會因為頁面突然關掉就消失(除非使用者手動清除快取)。IDBObjectStore
在 Transaction 中會拿到 Object Store(資料表),提供基本 CRUD 方法:
add(value)
:新增一筆資料。get(key)
:根據主鍵讀取單筆。getAll()
:一次讀取多筆。put(value)
:新增或更新(若主鍵存在則覆蓋)。delete(key)
:刪除指定主鍵的資料。注意:所有這些操作,都必須包在 transaction 裡,才能保證 ACID 特性。
function addNote(db, content) {
// 建立一個 "readwrite" 交易(transaction),才能進行新增/更新/刪除
const tx = db.transaction("notes", "readwrite");
// 從交易中取得目標 Object Store
const store = tx.objectStore("notes");
// 新增一筆資料到 Object Store
// 若該 store 在建立時使用了 { keyPath: "id", autoIncrement: true },
// 則沒提供 id 也會自動生成遞增主鍵
store.add({ content, createdAt: Date.now() });
tx.oncomplete = () => console.log("✅ 新增完成");
tx.onerror = () => console.error("❌ 新增失敗");
}
讀取全部:
function getAllNotes(db) {
// 建立只讀交易(readonly),用於查詢不修改資料
const tx = db.transaction("notes", "readonly");
// 從交易中取得目標 Object Store
const store = tx.objectStore("notes");
// 發出「取得全部資料」的請求(非同步)
const request = store.getAll();
request.onsuccess = () => {
console.log("📒 筆記列表:", request.result);
};
}
讀取單筆:
store.get(1); // 主鍵為 1 的資料
store.put({ id: 1, content: "修改後的內容", createdAt: Date.now() });
刪除全部:
store.clear();
刪除單筆:
store.delete(1);
上面我們用簡單程式碼展示了最基本的 CRUD 流程。
想直接體驗完整的 IndexedDB CRUD 效果,可以參考這個線上範例 - 我的筆記(同時也是本文截圖的來源): 📝
今天我們打下 IndexedDB 的基礎:
下一篇(Day 23),我們會進階:
👉 歡迎追蹤這個系列,我會從 Canvas 開始,一步步帶你認識更多 Web API 🎯