iT邦幫忙

2025 iThome 鐵人賽

DAY 22
3
Modern Web

從 Canvas 到各式各樣的 Web API 之旅系列 第 22

Day 22 - IndexedDB 初探:瀏覽器裡的資料庫

  • 分享至 

  • xImage
  •  

有沒有遇過這些需求:希望筆記離線也能開、想把大量資料快取下來讓頁面秒開、希望圖片能快取而不是每次都打 API、甚至在前端就能做搜尋或分類?這時候,瀏覽器內建的重量級儲存工具 IndexedDB 就派上用場了。📦


為什麼需要 IndexedDB?

前端常見的本地儲存方式,可以依照容量、使用場景與限制來比較:

Cookie

  • 容量小:通常僅幾 KB。
  • 傳輸特性:可以設定每次 HTTP 請求都會自動附帶,容易增加流量與安全風險。
  • 適合用途:存放「與伺服器溝通相關的小量資訊」(如登入 token、偏好設定)。
  • 不適合:存放大量資料。

localStorage / sessionStorage

  • 容量:約 5MB。
  • 操作方式:介面簡單,但只能存字串(物件要自行序列化/反序列化)。
  • 效能問題:API 為同步呼叫,大量存取可能卡住主執行緒,影響流暢度。
  • 限制:僅能用 key-value 存取,缺乏索引與查詢能力。

IndexedDB

  • 容量大:可達數十到數百 MB(依瀏覽器與磁碟空間而定)。
  • 資料型別:能直接存放 JavaScript 物件,不必手動轉字串;也能存檔案、Blob。
  • 查詢能力:支援索引(Index)與條件查詢,適合做搜尋、分類、分頁。
  • 效能:非同步 API,不會阻塞主執行緒。
  • 可靠性:透過 Transaction 確保資料讀寫一致性。

所以,大型資料快取、離線可用、前端搜尋/分類IndexedDB 才是最合適的選擇


IndexedDB 是什麼?

IndexedDB 就像瀏覽器內建的 NoSQL 資料庫

  • Database:一個名稱對應一個資料庫。
  • Object Store:類似資料表,可以存放多筆物件。
  • Index:幫助快速查詢欄位。
  • Transaction:所有讀寫必須在 transaction 裡進行,確保一致性。

和 SQL DB 不同的是,你不需要先定義 schema,再 insert 一大堆 row;你可以直接把 JS 物件塞進去,瀏覽器會幫你管理。


建立資料庫與 Object Store

indexedDB.open(name, version)

  • 用來打開或建立資料庫。
  • name:資料庫名稱。
  • version:版本號,schema 變動要升級版本。

IDBDatabase

  • 打開成功後會拿到 db 物件。
  • 代表一個資料庫,可以用來建立 transaction。

db.createObjectStore(name, options)

  • 建立 Object Store(類似 SQL 的 table)。
  • keyPath:指定物件裡哪個欄位是主鍵。
  • autoIncrement:若沒提供主鍵,瀏覽器自動生成遞增 id。

IDBRequest

  • 幾乎所有操作都是非同步,會回傳 request
  • request.onsuccessrequest.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 模式

在 IndexedDB 裡,所有操作都必須透過 Transaction(交易)
就像在銀行裡,你要辦理存款、提款,一定要先「抽號碼牌 → 進入櫃台」;而 Transaction 就是這個「櫃台流程」。

db.transaction(storeNames, mode)

  • readonly:只讀,一般 R 都會用這個。
  • readwrite:可讀寫,一般 CUD 都會用這個。
  • versionchange:只有在升級 schema(onupgradeneeded)時才會用到。

Transaction 的特性

Transaction 擁有資料庫常見的 ACID 四大保證

  • Atomicity(原子性)
    同一個 transaction 內的操作,要嘛全部成功,要嘛全部失敗。
    例如一次新增三筆資料,若其中一筆出錯,不會只進去兩筆,整批都會回滾。
  • Consistency(一致性)
    瀏覽器會維護主鍵與索引的規則。
    假設一個欄位必須唯一,如果你硬塞重複值,整個 transaction 會被取消,確保資料不亂掉。
  • Isolation(隔離性)
    IndexedDB 會自動排隊序列化 readwrite 交易:同一個 Object Store 不會同時有兩個寫操作在跑,避免衝突。
    readonly 交易可以並行,但看不到尚未提交的寫入。
  • Durability(持久性)
    transaction.oncomplete 事件觸發時,表示資料已經真正寫入本地儲存,不會因為頁面突然關掉就消失(除非使用者手動清除快取)。

IDBObjectStore

在 Transaction 中會拿到 Object Store(資料表),提供基本 CRUD 方法:

  • add(value):新增一筆資料。
  • get(key):根據主鍵讀取單筆。
  • getAll():一次讀取多筆。
  • put(value):新增或更新(若主鍵存在則覆蓋)。
  • delete(key):刪除指定主鍵的資料。

注意:所有這些操作,都必須包在 transaction 裡,才能保證 ACID 特性。


CRUD

Create 新增資料

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("❌ 新增失敗");
}

indexedDB-demo

Read 讀取資料

讀取全部:

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 的資料

Update 更新

store.put({ id: 1, content: "修改後的內容", createdAt: Date.now() });

indexedDB-demo

Delete 刪除

刪除全部:

store.clear();

刪除單筆:

store.delete(1);

indexedDB-demo


限制與注意事項

  • 容量限制:通常每個來源(origin)能存 50MB~幾百 MB,取決於瀏覽器與磁碟空間。
  • 不同瀏覽器差異:Safari 對 IndexedDB 的穩定性較弱,可能需要額外測試。

應用情境

常見應用場景

  • PWA 快取 API 回應:使用 IndexedDB 快取資料,比 localStorage 更大、更靈活。
  • 離線記事本:使用者斷網時先寫到 IndexedDB,恢復網路後再同步伺服器。
  • 前端搜尋引擎:資料先存 IndexedDB,再用索引加速查詢。

誰在用 IndexedDB?知名案例

  • Google Docs / Google Drive:支援離線模式,檔案可在無網路時開啟與編輯。
  • Gmail:快取郵件內容,收件匣可離線瀏覽。
  • Figma:設計稿與操作快取,網路中斷時依然能工作。
  • VS Code for Web:使用 IndexedDB 儲存專案檔案與設定。
  • 各種 PWA(如 Twitter、Spotify Web App):快取 API 回應與使用者資料,提升啟動速度。

範例 Demo

上面我們用簡單程式碼展示了最基本的 CRUD 流程。
想直接體驗完整的 IndexedDB CRUD 效果,可以參考這個線上範例 - 我的筆記(同時也是本文截圖的來源): 📝


小結

今天我們打下 IndexedDB 的基礎:

  • 它是瀏覽器內建的 NoSQL 資料庫
  • 支援大量資料、物件儲存、索引、transaction。
  • 認識了 transaction 模式、object store、基本 CRUD。
  • 知道它的限制與適用場景。

下一篇(Day 23),我們會進階:

  • 如何建立索引?
  • 如何用 Cursor 做分頁?
  • 怎麼封裝 API,避免 callback hell?
  • 以及 Dexie.js 這類工具怎麼幫助我們。🔥

👉 歡迎追蹤這個系列,我會從 Canvas 開始,一步步帶你認識更多 Web API 🎯


上一篇
Day 21 - 量化網頁速度!用 Performance API 看載入、繪製、資源的效能表現
系列文
從 Canvas 到各式各樣的 Web API 之旅22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言