IndexedDB 是一種在瀏覽器中提供事務性的鍵值對存儲的低級 API。它允許你在用户的瀏覽器中存儲大量結構化數據,並且可以對其進行高效的搜索、更新和刪除操作。IndexedDB 適用於需要離線存儲和快速訪問大量數據的應用程序,如 Progressive Web Apps (PWAs) 和單頁應用程序 (SPAs)。本文將詳細介紹如何在前端項目中高效使用 IndexedDB。
1. IndexedDB 基本概念
1.1 數據庫
數據庫是存儲對象集合的地方。每個數據庫都有一個名稱和版本號。
1.2 對象存儲空間(Object Store)
對象存儲空間類似於關係型數據庫中的表。每個對象存儲空間可以包含一組對象,並且每個對象都有一個唯一的鍵。
1.3 索引(Index)
索引用於快速查找對象存儲空間中的對象。索引可以基於對象的屬性創建,從而加速查詢操作。
1.4 事務(Transaction)
事務是一組操作的集合,這些操作要麼全部成功,要麼全部失敗。事務可以保證數據的一致性和完整性。
1.5 請求(Request)
請求用於執行數據庫操作,如添加、刪除或獲取數據。請求是異步的,可以通過事件監聽器處理結果。
2. IndexedDB 基本操作
2.1 打開數據庫
const request = indexedDB.open('myDatabase', 1);
request.onupgradeneeded = function(event) {
const db = event.target.result;
// 創建對象存儲空間
if (!db.objectStoreNames.contains('myStore')) {
const objectStore = db.createObjectStore('myStore', { keyPath: 'id', autoIncrement: true });
// 創建索引
objectStore.createIndex('nameIndex', 'name', { unique: false });
}
};
request.onsuccess = function(event) {
const db = event.target.result;
console.log('Database opened successfully');
};
request.onerror = function(event) {
console.error('Database error:', event.target.errorCode);
};
2.2 添加數據
function addData(db, data) {
const transaction = db.transaction(['myStore'], 'readwrite');
const objectStore = transaction.objectStore('myStore');
const request = objectStore.add(data);
request.onsuccess = function(event) {
console.log('Data added successfully');
};
request.onerror = function(event) {
console.error('Error adding data:', event.target.errorCode);
};
}
// 使用示例
const db = request.result;
addData(db, { name: 'John Doe', age: 30 });
2.3 獲取數據
function getData(db, id) {
const transaction = db.transaction(['myStore'], 'readonly');
const objectStore = transaction.objectStore('myStore');
const request = objectStore.get(id);
request.onsuccess = function(event) {
const data = event.target.result;
console.log('Data retrieved:', data);
};
request.onerror = function(event) {
console.error('Error retrieving data:', event.target.errorCode);
};
}
// 使用示例
getData(db, 1);
2.4 更新數據
function updateData(db, id, newData) {
const transaction = db.transaction(['myStore'], 'readwrite');
const objectStore = transaction.objectStore('myStore');
const request = objectStore.get(id);
request.onsuccess = function(event) {
const data = event.target.result;
if (data) {
Object.assign(data, newData);
const updateRequest = objectStore.put(data);
updateRequest.onsuccess = function() {
console.log('Data updated successfully');
};
updateRequest.onerror = function(event) {
console.error('Error updating data:', event.target.errorCode);
};
} else {
console.error('Data not found');
}
};
request.onerror = function(event) {
console.error('Error retrieving data:', event.target.errorCode);
};
}
// 使用示例
updateData(db, 1, { age: 31 });
2.5 刪除數據
function deleteData(db, id) {
const transaction = db.transaction(['myStore'], 'readwrite');
const objectStore = transaction.objectStore('myStore');
const request = objectStore.delete(id);
request.onsuccess = function(event) {
console.log('Data deleted successfully');
};
request.onerror = function(event) {
console.error('Error deleting data:', event.target.errorCode);
};
}
// 使用示例
deleteData(db, 1);
3. 使用 Promise 封裝 IndexedDB 操作
為了簡化異步操作,可以使用 Promise 封裝 IndexedDB 的基本操作。
function openDatabase(name, version) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(name, version);
request.onupgradeneeded = function(event) {
const db = event.target.result;
if (!db.objectStoreNames.contains('myStore')) {
const objectStore = db.createObjectStore('myStore', { keyPath: 'id', autoIncrement: true });
objectStore.createIndex('nameIndex', 'name', { unique: false });
}
};
request.onsuccess = function(event) {
resolve(event.target.result);
};
request.onerror = function(event) {
reject(event.target.error);
};
});
}
function addData(db, data) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(['myStore'], 'readwrite');
const objectStore = transaction.objectStore('myStore');
const request = objectStore.add(data);
request.onsuccess = function(event) {
resolve(event.target.result);
};
request.onerror = function(event) {
reject(event.target.error);
};
});
}
function getData(db, id) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(['myStore'], 'readonly');
const objectStore = transaction.objectStore('myStore');
const request = objectStore.get(id);
request.onsuccess = function(event) {
resolve(event.target.result);
};
request.onerror = function(event) {
reject(event.target.error);
};
});
}
function updateData(db, id, newData) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(['myStore'], 'readwrite');
const objectStore = transaction.objectStore('myStore');
const request = objectStore.get(id);
request.onsuccess = function(event) {
const data = event.target.result;
if (data) {
Object.assign(data, newData);
const updateRequest = objectStore.put(data);
updateRequest.onsuccess = function() {
resolve();
};
updateRequest.onerror = function(event) {
reject(event.target.error);
};
} else {
reject(new Error('Data not found'));
}
};
request.onerror = function(event) {
reject(event.target.error);
};
});
}
function deleteData(db, id) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(['myStore'], 'readwrite');
const objectStore = transaction.objectStore('myStore');
const request = objectStore.delete(id);
request.onsuccess = function(event) {
resolve();
};
request.onerror = function(event) {
reject(event.target.error);
};
});
}
3.1 使用示例
openDatabase('myDatabase', 1)
.then(db => {
return addData(db, { name: 'John Doe', age: 30 });
})
.then(id => {
console.log('Data added with ID:', id);
return getData(db, id);
})
.then(data => {
console.log('Data retrieved:', data);
return updateData(db, data.id, { age: 31 });
})
.then(() => {
console.log('Data updated successfully');
return deleteData(db, data.id);
})
.then(() => {
console.log('Data deleted successfully');
})
.catch(error => {
console.error('Error:', error);
});
4. 使用 idb 庫簡化操作
idb 是一個用於簡化 IndexedDB 操作的庫,提供了更簡潔的 API。
4.1 安裝 idb
npm install idb
4.2 使用 idb 進行操作
import { openDB } from 'idb';
async function openDatabase() {
const db = await openDB('myDatabase', 1, {
upgrade(db) {
if (!db.objectStoreNames.contains('myStore')) {
const objectStore = db.createObjectStore('myStore', { keyPath: 'id', autoIncrement: true });
objectStore.createIndex('nameIndex', 'name', { unique: false });
}
},
});
return db;
}
async function addData(db, data) {
const tx = db.transaction('myStore', 'readwrite');
const store = tx.objectStore('myStore');
await store.add(data);
await tx.done;
}
async function getData(db, id) {
const tx = db.transaction('myStore', 'readonly');
const store = tx.objectStore('myStore');
return store.get(id);
}
async function updateData(db, id, newData) {
const tx = db.transaction('myStore', 'readwrite');
const store = tx.objectStore('myStore');
const data = await store.get(id);
if (data) {
Object.assign(data, newData);
await store.put(data);
} else {
throw new Error('Data not found');
}
await tx.done;
}
async function deleteData(db, id) {
const tx = db.transaction('myStore', 'readwrite');
const store = tx.objectStore('myStore');
await store.delete(id);
await tx.done;
}
// 使用示例
(async () => {
try {
const db = await openDatabase();
const id = await addData(db, { name: 'John Doe', age: 30 });
console.log('Data added with ID:', id);
const data = await getData(db, id);
console.log('Data retrieved:', data);
await updateData(db, data.id, { age: 31 });
console.log('Data updated successfully');
await deleteData(db, data.id);
console.log('Data deleted successfully');
} catch (error) {
console.error('Error:', error);
}
})();
5. 高效使用 IndexedDB 的最佳實踐
5.1 使用事務
儘量將多個操作放在同一個事務中,以減少事務的開銷並提高性能。
5.2 創建索引
為經常查詢的字段創建索引,可以顯著提高查詢速度。
5.3 數據分片
如果需要存儲大量數據,可以考慮將數據分片存儲在不同的對象存儲空間中。
5.4 異步操作
IndexedDB 是異步的,充分利用異步操作可以避免阻塞主線程,提高應用的響應速度。
5.5 錯誤處理
始終為每個請求添加錯誤處理邏輯,以便在出現問題時能夠及時捕獲並處理。
5.6 數據版本管理
使用版本號來管理數據庫的升級和降級,確保數據的一致性和完整性。
6. 示例:構建一個簡單的待辦事項應用
下面是一個使用 IndexedDB 構建的簡單待辦事項應用的示例。
6.1 HTML 結構
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo App</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
#todo-form {
margin-bottom: 20px;
}
#todo-list {
list-style-type: none;
padding: 0;
}
.todo-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.todo-item button {
margin-left: 10px;
}
</style>
</head>
<body>
<h1>Todo List</h1>
<form id="todo-form">
<input type="text" id="todo-input" placeholder="Add a new todo" required>
<button type="submit">Add</button>
</form>
<ul id="todo-list"></ul>
<script src="app.js"></script>
</body>
</html>
6.2 JavaScript 代碼 (app.js)
import { openDB } from 'idb';
let db;
async function openDatabase() {
db = await openDB('todoDatabase', 1, {
upgrade(db) {
if (!db.objectStoreNames.contains('todos')) {
db.createObjectStore('todos', { keyPath: 'id', autoIncrement: true });
}
},
});
}
async function addTodo(todo) {
const tx = db.transaction('todos', 'readwrite');
const store = tx.objectStore('todos');
await store.add(todo);
await tx.done;
renderTodos();
}
async function getTodos() {
const tx = db.transaction('todos', 'readonly');
const store = tx.objectStore('todos');
return store.getAll();
}
async function deleteTodo(id) {
const tx = db.transaction('todos', 'readwrite');
const store = tx.objectStore('todos');
await store.delete(id);
await tx.done;
renderTodos();
}
async function renderTodos() {
const todos = await getTodos();
const todoList = document.getElementById('todo-list');
todoList.innerHTML = '';
todos.forEach(todo => {
const li = document.createElement('li');
li.className = 'todo-item';
li.textContent = todo.text;
const deleteButton = document.createElement('button');
deleteButton.textContent = 'Delete';
deleteButton.addEventListener('click', () => deleteTodo(todo.id));
li.appendChild(deleteButton);
todoList.appendChild(li);
});
}
document.getElementById('todo-form').addEventListener('submit', async (event) => {
event.preventDefault();
const input = document.getElementById('todo-input');
const text = input.value.trim();
if (text) {
await addTodo({ text });
input.value = '';
}
});
(async () => {
await openDatabase();
renderTodos();
})();
6.3 解釋
-
HTML 結構:
- 包含一個表單用於添加新的待辦事項。
- 包含一個無序列表用於顯示所有待辦事項。
-
JavaScript 代碼:
- 使用
idb庫簡化 IndexedDB 的操作。 - 打開數據庫並創建
todos對象存儲空間。 - 提供
addTodo、getTodos和deleteTodo函數來操作數據。 renderTodos函數用於渲染待辦事項列表。- 表單提交事件監聽器用於添加新的待辦事項。
- 使用