一、常规用法
1.特性
- 大容量存储
支持存储GB级数据(通常为浏览器可用磁盘的50%),远超LocalStorage的5MB限制。
适合离线应用缓存、音视频资源本地存储等场景。
- 异步事务模型
所有操作通过异步API执行,避免阻塞主线程。
支持读写事务(readwrite)和只读事务(readonly),保证数据操作的原子性。(数据库事务的定义)
- 高效索引查询
支持单字段/多字段索引,通过createIndex创建索引提升查询效率。
支持范围查询、模糊匹配(通过游标遍历)。
- 复杂数据结构支持
可存储JavaScript对象、Blob、File等数据类型,通过键值对管理数据。
- 版本控制与迁移
通过onupgradeneeded事件实现数据库版本升级,支持数据结构变更(如新增对象仓库)。
2.创建,打开数据库
// 初始化数据库
initDB() {
return new Promise((resolve, reject) => {
// 打开数据库
const request = indexedDB.open(this.dbName, this.dbVersion);
// 数据库首次创建或版本更新时调用
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 创建对象仓库(类似于关系型数据库中的表)
if (!db.objectStoreNames.contains('todos')) {
const store = db.createObjectStore('todos', {
keyPath: 'id',
autoIncrement: true
});
// 创建单独的索引
store.createIndex('timestamp', 'timestamp', { unique: false });
store.createIndex('completed', 'completed', { unique: false });
// 创建复合索引
store.createIndex('completed_timestamp', ['completed', 'timestamp'], { unique: false });
}
};
// 数据库打开成功
request.onsuccess = (event) => {
this.db = event.target.result;
console.log('数据库连接成功');
resolve();
};
// 数据库打开失败
request.onerror = (event) => {
console.error('数据库连接失败:', event.target.error);
reject(event.target.error);
};
});
}
2.1 使用IndexDB.open打开indexDB数据库
- 需要指定数据库名称和版本号
- 返回一个**IDBRequest**对象
const request = indexedDB.open(this.dbName, this.dbVersion);
2.2 创建store,类似于SQL的table表(数据一条条存放在store)。
一般在IndexDB的onupgradeneeded成功回调中创建。
注意做好判断,不要重复创建
onupgradeneeded当IndexDB第一次打开或者版本号升级时候触发的回调
创建store可以设置
- 主键,自增
- 索引
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('todos')) {
const store = db.createObjectStore('todos', {
keyPath: 'id', // 定义主键 id
autoIncrement: true // 主键自增
});
}
};
2.3创建store索引
传入三个参数
- 索引名称
- 索引对应的对象的key
- 配置options
store.createIndex(indeName, keyPath, { unique: false });
3.查get(key),getAll()
IndexDB的**增删改查操作都需要通过事务**来完成,并且是异步的,不会阻塞主线程。
- 创建**readonly**的事务
IDBRequest.transaction([storeName], 'readonly'|' readwrite ')
// transaction
const transaction = this.db.transaction(['todos'], 'readonly');
const store = transaction.objectStore('todos');
- 事务绑定到todos这个store上
- **store.getAll()**查询所有数据
const request = store.getAll();
4.store.get(key),根据主键查询对应的数据
const getRequest = store.get(id);
3.新增add(object)
1.使用**readwrite**事务
2.调用**store.add(Object)**。内部先克隆在新增,不必担心引用共享的问题。
// 添加待办事项
async addTodo(text) {
const todo = {
text,
completed: false,
timestamp: Date.now()
};
return new Promise((resolve, reject) => {
// 1.创建读写事务
const transaction = this.db.transaction(['todos'], 'readwrite');
const store = transaction.objectStore('todos');
// 2.add数据
const request = store.add(todo);
// 监听成功和失败
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
4.删除delete(key)
- 创建readwrite读写事务
- store.delete(key) 根据主键删除对应的数据
// 删除待办事项
async deleteTodo(id) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['todos'], 'readwrite');
const store = transaction.objectStore('todos');
const request = store.delete(id);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
5.修改put(value, key?)
- 创建**readwrite**读写事务
- store.put(value, key?)。key可以不传,name直接value中需要含有主键,才能更新对应的数据
// 更新待办事项状态
async updateTodo(id, changes) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['todos'], 'readwrite');
const store = transaction.objectStore('todos');
// 先获取原有数据
const getRequest = store.get(id);
getRequest.onsuccess = () => {
const todo = { ...getRequest.result, ...changes };
const updateRequest = store.put(todo);
updateRequest.onsuccess = () => resolve(updateRequest.result);
updateRequest.onerror = () => reject(updateRequest.error);
};
getRequest.onerror = () => reject(getRequest.error);
});
}
二、高级特性
1.游标Cursor
类似于指针
- 创建游标
IDBObjectStore.openCursor()。store对象可以创建游标
下文的索引对象也可以创建游标。IDBIndex.openCursor()
- event.target.value,event.target.key 获取指针指向当前对象的key和value
- continue() 移动游标到下一位
const todos = [];
const transaction = this.db.transaction(['todos'], 'readonly');
const store = transaction.objectStore('todos');
// 打开游标
const request = store.openCursor(range);
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
todos.push(cursor.value);
cursor.continue(); // 移动到下一条记录
} else {
resolve(todos); // 遍历完成
}
};
request.onerror = () => reject(request.error);
2.索引的使用
- 创建索引
- 创建范围
// 范围查询
const range = IDBKeyRange.bound(startDate, endDate);
// 精确匹配
const range = IDBKeyRange.only(value);
// 大于等于
const range = IDBKeyRange.lowerBound(value);
// 小于等于
const range = IDBKeyRange.upperBound(value);
- 结合游标查询数据
案例:
// 创建事务
const transaction = this.db.transaction(['todos'], 'readonly');
const store = transaction.objectStore('todos');
// 使用 completed 索引
const index = store.index('completed');
const range = IDBKeyRange.only(completed);
// 使用游标遍历 索引检索出来的数据
const request = index.openCursor(range);
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
todos.push(cursor.value);
cursor.continue();
} else {
resolve(todos);
}
};
三、源码
1.index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IndexDB 待办事项</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>IndexDB 待办事项</h1>
<div class="todo-input">
<input type="text" id="todoInput" placeholder="输入待办事项...">
<button onclick="addTodo()">添加</button>
</div>
<ul id="todoList"></ul>
</div>
<script src="db.js"></script>
<script src="app.js"></script>
</body>
</html>
2.样式style.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
background-color: #f5f5f5;
}
.container {
max-width: 600px;
margin: 2rem auto;
padding: 1rem;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 2rem;
}
.todo-input {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
}
input[type="text"] {
flex: 1;
padding: 0.5rem;
font-size: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
padding: 0.5rem 1rem;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
#todoList {
list-style: none;
}
.todo-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: white;
margin-bottom: 0.5rem;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.todo-item.completed {
background-color: #f8f8f8;
text-decoration: line-through;
color: #888;
}
.delete-btn {
background-color: #ff4444;
}
.delete-btn:hover {
background-color: #cc0000;
}
3.app.js 用户交互
// DOM 元素
const todoInput = document.getElementById('todoInput')
const todoList = document.getElementById('todoList')
// 页面加载完成后显示所有代办事项
document.addEventListener('DOMContentLoaded', () => {
setTimeout(async () => {
await loadTodos()
}, 100)
})
// 加载所有代办事项
async function loadTodos() {
try {
const todos = await todoDB.getAllTodos()
renderTodos(todos)
} catch (error) {
console.error('加载待办事项失败:', error)
}
}
// 渲染代办事项列表
function renderTodos(todos) {
todoList.innerHTML = ''
todos.forEach((todo) => {
const li = document.createElement('li')
li.className = `todo-item ${todo.completed ? 'completed' : ''}`
// 创建待办事项文本
const span = document.createElement('span')
span.textContent = todo.text
// 创建操作按钮容器
const actions = document.createElement('div')
// 创建完成按钮
const completeBtn = document.createElement('button')
completeBtn.textContent = todo.completed ? '取消完成' : '完成'
completeBtn.onclick = () => toggleTodo(todo.id, todo.completed ? 0 : 1)
// 创建删除按钮
const deleteBtn = document.createElement('button')
deleteBtn.textContent = '删除'
deleteBtn.className = 'delete-btn'
deleteBtn.onclick = () => deleteTodo(todo.id)
// 组装元素
actions.appendChild(completeBtn)
actions.appendChild(deleteBtn)
li.appendChild(span)
li.appendChild(actions)
todoList.appendChild(li)
})
}
// 添加待办事项
async function addTodo() {
const text = todoInput.value.trim()
if (!text) return
try {
await todoDB.addTodo(text)
todoInput.value = ''
await loadTodos()
} catch (error) {
console.error('添加待办事项失败:', error)
}
}
// 切换待办事项状态
async function toggleTodo(id, completed) {
try {
await todoDB.updateTodo(id, { completed })
await loadTodos()
} catch (error) {
console.error('更新待办事项状态失败:', error)
}
}
// 删除待办事项
async function deleteTodo(id) {
try {
await todoDB.deleteTodo(id)
await loadTodos()
} catch (error) {
console.error('删除待办事项失败:', error)
}
}
todoInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
addTodo()
}
})
// 点击查看当天已完成的
const checkTodayBtn = document.getElementById('checkTodayBtn')
checkTodayBtn.addEventListener('click', async () => {
const start = new Date(2025, 3, 8, 0, 0, 0, 0)
const end = new Date(2025, 3, 8, 23, 59, 59, 0)
alert(JSON.stringify(await todoDB.getTodosByDateRange(start, end)))
})
// 点击已完成或者未完成
const checkCompleteBtn = document.getElementById('checkCompleteBtn')
checkCompleteBtn.addEventListener('click', async (e) => {
const data = e.target.dataset.complete
const res = await todoDB.getTodosByStatus(1)
alert(JSON.stringify(res))
})
// 点击已完成或者未完成
const deleteBatchBtn = document.getElementById('deleteBatch')
deleteBatchBtn.addEventListener('click', async (e) => {
// 获取全部id
const ids = (await todoDB.getAllTodos()).map((item) => item.id)
const res = await todoDB.deleteTodos(ids)
loadTodos()
})
4.db.js indexDB数据库操作
// IndexDB 数据库操作类
class TodoDB {
constructor() {
this.dbName = 'TodoDB'
this.dbVersion = 1
this.db = null
// 初始化数据库
this.initDB()
}
// 初始化数据库
initDB() {
return new Promise((resolve, reject) => {
// 打开数据库
const request = indexedDB.open(this.dbName, this.dbVersion)
// 数据库首次创建或者版本更新时调用
request.onupgradeneeded = (event) => {
const db = event.target.result
// 创建对象仓库(类似于关系型数据库中的表)
if (!db.objectStoreNames.contains('todos')) {
const store = db.createObjectStore('todos', {
keyPath: 'id',
autoIncrement: true
})
// 创建单独索引
store.createIndex('timestamp', 'timestamp', { unique: false })
store.createIndex('completed', 'completed', { unique: false })
// 创建复合索引
store.createIndex('completed_timestamp', ['timestamp', 'completed'], {
unique: false
})
}
}
request.onsuccess = (event) => {
this.db = event.target.result
console.log('数据库连接成功')
resolve()
}
request.onerror = (event) => {
console.error('数据库连接失败:', event.target.error)
reject(event.target.error)
}
})
}
// 添加待办事项
async addTodo(text) {
const todo = {
text,
completed: 0,
timestamp: Date.now()
}
return new Promise((resolve, reject) => {
// 开启事务
const transaction = this.db.transaction(['todos'], 'readwrite')
// 获取对象的存储空间
const store = transaction.objectStore('todos')
const request = store.add(todo)
request.onsuccess = () => {
resolve(request.result)
}
request.onerror = () => {
reject(request.error)
}
})
}
// 获取所有代办事项
async getAllTodos() {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['todos'], 'readonly')
const store = transaction.objectStore('todos')
const request = store.getAll()
request.onsuccess = () => resolve(request.result)
request.onerror = () => reject(request.error)
})
}
// 更新待办事项
async updateTodo(id, changes) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['todos'], 'readwrite')
const store = transaction.objectStore('todos')
// 先获取原有数据
const r = store.get(1744354409809)
r.onsuccess = (e) => {
console.log(r.result)
}
const getRequest = store.get(id)
getRequest.onsuccess = () => {
const todo = { ...getRequest.result, ...changes }
const updateRequest = store.put(todo)
updateRequest.onsuccess = () => resolve(updateRequest.result)
updateRequest.onerror = () => reject(updateRequest.error)
}
getRequest.onerror = () => reject(getRequest.error)
})
}
// 删除待办事项
async deleteTodo(id) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['todos'], 'readwrite')
const store = transaction.objectStore('todos')
const request = store.delete(id)
request.onsuccess = () => resolve()
request.onerror = () => reject(request.error)
})
}
// 使用游标获取指定时间范围的代办事项
getTodosByDateRange(startDate, endDate) {
return new Promise((resolve, reject) => {
const todos = []
const transaction = this.db.transaction(['todos'], 'readonly')
console.log(transaction)
const store = transaction.objectStore('todos')
console.log(store)
const index = store.index('timestamp')
console.log(index)
// 创建一个范围
const range = IDBKeyRange.bound(startDate.getTime(), endDate.getTime())
console.log(range)
// 打开游标
const request = index.openCursor(range)
request.onsuccess = (event) => {
const cursor = event.target.result
console.log('cursor', cursor)
if (cursor) {
todos.push(cursor.value)
cursor.continue() // 移动到下一条记录
} else {
resolve(todos) // 遍历完成
}
}
})
}
// 使用复合索引获取已完成、未完成的待办事项,并按时间排序
async getTodosByStatus(completed) {
return new Promise((resolve, reject) => {
const todos = []
const transaction = this.db.transaction(['todos'], 'readonly')
const store = transaction.objectStore('todos')
// 使用 completed 索引而不是复合索引
const index = store.index('completed')
const range = IDBKeyRange.only(1)
// 使用游标遍历
const request = index.openCursor(range)
request.onsuccess = (event) => {
const cursor = event.target.result
if (cursor) {
todos.push(cursor.value)
cursor.continue()
} else {
// 在内存中按时间戳排序
todos.sort((a, b) => b.timestamp - a.timestamp)
resolve(todos)
}
}
request.onerror = () => reject(request.error)
})
}
// 批量删除待办事项
deleteTodos(ids) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['todos'], 'readwrite')
const store = transaction.objectStore('todos')
let completed = 0
let errors = []
// 监听事务完成
transaction.oncomplete = () => {
if (errors.length > 0) {
reject(errors)
} else {
resolve()
}
}
// 在同一事务中执行所有删除操作
ids.forEach((id) => {
const request = store.delete(id)
request.onsuccess = () => completed++
request.onerror = () => errors.push(request.error)
})
})
}
}
// 创建数据库实例
const todoDB = new TodoDB()