使用 IndexedDB 緩存大量資料

起因是撰寫的油猴腳本使用localStorage存資料,到某一天發現資料怎麼存不進去了,排查才發現資料大於5MB了,然後就開始找有沒有其他更好的存資料方式,然後就發現了瀏覽器資料庫 API - IndexedDB。如果你正在開發需要在前端儲存大量資料的網頁應用程式,那麼這篇文章絕對不能錯過!

什麼是 IndexedDB?

IndexedDB 是一個低階的 API,用於在瀏覽器中儲存大量結構化資料。它允許你在使用者的瀏覽器中儲存幾乎無限量的資料,而且速度超快!與 localStorage 不同,IndexedDB 可以處理更複雜的資料結構,還支援索引搜尋,簡直是前端開發者的好朋友啊!

為什麼要學 IndexedDB?

  1. 儲存大量資料:相較於 localStorage 的 5MB 限制,IndexedDB 幾乎沒有儲存上限。
  2. 支援複雜資料結構:可以儲存和查詢 JavaScript 物件。
  3. 非同步操作:不會阻塞主執行緒,讓你的應用程式更流暢。
  4. 支援交易(Transactions):確保資料操作的一致性和可靠性。

IndexedDB 基本概念

在開始寫程式之前,我們先來了解一下 IndexedDB 的幾個重要概念:

  1. 資料庫(Database):就像一個大容器,裡面可以存放多個物件儲存區。
  2. 物件儲存區(Object Store):類似資料表,用來儲存資料。
  3. 索引(Index):用來加速搜尋特定欄位的值。
  4. 交易(Transaction):確保一系列操作全部成功或全部失敗。
  5. 游標(Cursor):用來遍歷資料集合的機制。

實作教學

好啦,廢話不多說,我們直接來寫程式吧!

1. 開啟資料庫連線

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let db;
const dbName = "MyAwesomeDB";

const request = indexedDB.open(dbName, 1);

request.onerror = function(event) {
console.error("哎呀!開啟資料庫時發生錯誤:", event.target.error);
};

request.onsuccess = function(event) {
db = event.target.result;
console.log("耶!成功連線到資料庫");
};

request.onupgradeneeded = function(event) {
db = event.target.result;
const objectStore = db.createObjectStore("users", { keyPath: "id" });
console.log("資料庫結構升級完成");
};

這段程式碼會開啟一個名為 "MyAwesomeDB" 的資料庫,版本為 1。如果資料庫不存在或版本需要升級,就會觸發 onupgradeneeded 事件,在這裡我們創建了一個名為 "users" 的物件儲存區。

2. 新增資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function addUser(user) {
const transaction = db.transaction(["users"], "readwrite");
const objectStore = transaction.objectStore("users");
const request = objectStore.add(user);

request.onerror = function(event) {
console.error("新增資料失敗:", event.target.error);
};

request.onsuccess = function(event) {
console.log("讚啦!新增資料成功");
};
}

// 使用範例
addUser({ id: 1, name: "小明", email: "xiaoming@example.com" });

3. 讀取資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function getUser(id) {
const transaction = db.transaction(["users"]);
const objectStore = transaction.objectStore("users");
const request = objectStore.get(id);

request.onerror = function(event) {
console.error("讀取資料失敗:", event.target.error);
};

request.onsuccess = function(event) {
if (request.result) {
console.log("找到使用者:", request.result);
} else {
console.log("哎呀!找不到這個使用者");
}
};
}

// 使用範例
getUser(1);

4. 更新資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function updateUser(user) {
const transaction = db.transaction(["users"], "readwrite");
const objectStore = transaction.objectStore("users");
const request = objectStore.put(user);

request.onerror = function(event) {
console.error("更新資料失敗:", event.target.error);
};

request.onsuccess = function(event) {
console.log("讚啦!更新資料成功");
};
}

// 使用範例
updateUser({ id: 1, name: "小明", email: "xiaoming_new@example.com" });

5. 刪除資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function deleteUser(id) {
const transaction = db.transaction(["users"], "readwrite");
const objectStore = transaction.objectStore("users");
const request = objectStore.delete(id);

request.onerror = function(event) {
console.error("刪除資料失敗:", event.target.error);
};

request.onsuccess = function(event) {
console.log("掰掰!刪除資料成功");
};
}

// 使用範例
deleteUser(1);

進階技巧

1. 使用索引加速搜尋

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在 onupgradeneeded 中創建索引
objectStore.createIndex("nameIndex", "name", { unique: false });

// 使用索引搜尋
function getUserByName(name) {
const transaction = db.transaction(["users"]);
const objectStore = transaction.objectStore("users");
const index = objectStore.index("nameIndex");
const request = index.get(name);

request.onsuccess = function(event) {
if (request.result) {
console.log("找到使用者:", request.result);
} else {
console.log("哎呀!找不到這個使用者");
}
};
}

2. 使用游標遍歷資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function getAllUsers() {
const users = [];
const transaction = db.transaction(["users"]);
const objectStore = transaction.objectStore("users");
const request = objectStore.openCursor();

request.onsuccess = function(event) {
const cursor = event.target.result;
if (cursor) {
users.push(cursor.value);
cursor.continue();
} else {
console.log("所有使用者:", users);
}
};
}

注意事項

  1. 記得處理錯誤:IndexedDB 的操作可能會失敗,一定要好好處理錯誤狀況。
  2. 使用交易:盡可能將相關的操作放在同一個交易中,以確保資料一致性。
  3. 非同步思維:所有操作都是非同步的,要小心處理回調函式或使用 Promise。
  4. 版本控制:當需要修改資料庫結構時,要記得更新版本號碼。

結語

IndexedDB 雖然一開始看起來有點複雜,但是只要掌握了基本概念和用法,就能夠輕鬆地在前端儲存和管理大量資料,不過一般儲存使用localStorage還是夠用,畢竟我腳本是用了好幾年才到達5MB不得不換,資料少用localStorage還是很方便的,不過如果是持續增長的資料還是早換IndexedDB。