IndexedDB(概念、基本使用、Dexie.js的使用)
个人简介
👀个人主页: 前端杂货铺
🙋♂️学习方向: 主攻前端方向,正逐渐往全干发展
📃个人状态: 研发工程师,现效力于中国工业软件事业
🚀人生格言: 积跬步至千里,积小流成江海
🥇推荐学习:🍍前端面试宝典 🎨100个小功能 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js实战 🍒Three.js
🌕个人推广:每篇文章最下方都有加入方式,旨在交流学习&资源分享,快加入进来吧
文章目录
- 何为IndexedDB?
- IndexedDB的基本使用
- add
- update
- get
- delete
- Dexie - IndexedDB的极简包装
- IndexedDB的对比及使用场景
- 对比
- 使用场景
- 总结
何为IndexedDB?
本篇文章的练习demo已放置GitHub。dexie-use-demo · GitHub
IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 使用索引实现对数据的高性能搜索。虽然 Web Storage 在存储较少量的数据很有用,但对于存储更大量的结构化数据来说力不从心。而 IndexedDB 提供了这种场景的解决方案。(MDN_IndexedDB)
IndexedDB 是一个事务型数据库系统,它是一个基于 JavaScript 的面向对象数据库。IndexedDB 允许你存储和检索用键索引的对象;可以存储结构化克隆算法支持的任何对象。你只需要指定数据库模式,打开与数据库的连接,然后检索和更新一系列事务。
小贴士👨🎓 => 什么是事务?
数据库事务是一系列数据库操作(如插入、更新、删除等)的集合,这些操作作为一个不可分割的工作单元执行,确保数据库的完整性和一致性。事务具有原子性、一致性、隔离性和持久性四个基本特性(ACID)。
- 原子性(Atomicity):事务中的所有操作必须全部完成或全部不执行,确保数据不会因部分操作失败而处于不一致状态。
- 一致性(Consistency):事务执行前后,数据库需保持一致状态,中间过程对外部不可见。
- 隔离性(Isolation):多个事务并发执行时,彼此操作互不干扰,防止数据错误。
- 持久性(Durability):事务提交后,对数据库的修改永久保存,即使系统故障也不会丢失。
可简化IndexedDB使用的相关库: localForage、dexie.js、PouchDB、idb、idb-keyval、JsStore 或者 lovefield。
在 浏览器-应用-存储 可以看到今天的主角 IndexedDB。接下来,我们一起来推开 IndexedDB 的大门,看看里面的世界。
IndexedDB的基本使用
GitHub提交记录
我们打开开发者工具,点击 源代码/来源,点击 代码段,点击 新代码段,添加名为 indexedDB 的代码段。
createObjectStore:IDBDatabase 接口的 createObjectStore() 方法创建并返回一个新的 object store 或 index。此方法接受一个参数作为 store 的名称,也接受一个可选的参数对象让你可以定义重要的可选属性。你可以用这个属性唯一的标识此 store 中的每个对象。因为参数是一个标识符,所以 store 中的每一个对象都应有此属性并保证此属性唯一。
接下来,我们编写如下代码,创建并打开名为 test 的数据库,并创建名为 user 的对象存储空间,设置 user_id
作为主键。
// 打开名为"test"的IndexedDB数据库,返回一个IDBOpenDBRequest对象
const db = window.indexedDB.open("test");
// 用于存储数据库连接
let connection;// 当数据库版本升级或首次创建时触发的事件
db.onupgradeneeded = (e) => {// 获取数据库连接connection = e.target.result;// 在数据库中创建一个名为'user'的对象存储空间(objectStore)// 设置'user_id'作为主键(keyPath)connection.createObjectStore('user', {keyPath: 'user_id'});
}// 成功时触发的事件
db.onsuccess = (e) => {// 获取数据库连接connection = e.target.result;console.log('connection success');
}// 失败时触发的事件
db.onerror = (e) => {console.log('connection error');
}
此时我们去右键 indexedDB,点击运行即可得到 test 数据库 和 user对象存储空间。
使用 IndexedDB 执行的操作是异步执行的,以免阻塞应用程序。
我们使用 setTimeout
模拟异步操作。
setTimeout(() => {}, 1000)
接下来我们学习如何进行 增删改查,注意我们把相关逻辑写入定时器内(模拟异步)。
更多实例方法(如数量、索引、键等)请参照 IDBObjectStore · MDN
add
IDBTransacation 接口由 IndexedDB API 提供,异步事务使用数据库中的事件对象属性。所有的读取和写入数据均在事务中完成。由 IDBDatabase 发起事务,通过 IDBTransaction 来设置事务的模式(例如:是否只读 readonly 或读写 readwrite),以及通过 IDBObjectStore 来发起一个请求。同时你也可以使用它来中止事务。
IndexedDB API 的 IDBObjectStore 接口表示数据库中的对象存储。对象存储中的记录根据其键值进行排序。这种排序可以实现快速插入、查找和有序检索。
setTimeout(() => {// 创建一个事务,指定要操作的对象存储空间为user,并设置事务模式为读写模式,允许读取和修改数据const tx = connection.transaction('user', 'readwrite');// 获取到 storeconst store = tx.objectStore('user');// addconst addReq = store.add({user_id: '1',user_name: '前端杂货铺'})addReq.onsuccess = () => {console.log('add success');}addReq.onerror = () => {console.log('add error');}
}, 1000)
运行代码段后,我们刷新 test
数据库,即可看到数据已写入。
update
setTimeout(() => {// 创建一个事务,指定要操作的对象存储空间为user,并设置事务模式为读写模式,允许读取和修改数据const tx = connection.transaction('user', 'readwrite');// 获取到storeconst store = tx.objectStore('user');// updateconst putReq = store.put({user_id: '1',user_name: '前端杂货铺2025'})putReq.onsuccess = () => {console.log('update success');}putReq.onerror = () => {console.log('update error');}
}, 1000)
get
setTimeout(() => {// 创建一个事务,指定要操作的对象存储空间为user,并设置事务模式为读写模式,允许读取和修改数据const tx = connection.transaction('user', 'readwrite');// 获取到storeconst store = tx.objectStore('user');// getconst getReq = store.get('1');getReq.onsuccess = (e) => {console.log(e.target.result);}getReq.onerror = () => {console.log('get error'); }
}, 1000)
delete
setTimeout(() => {// 创建一个事务,指定要操作的对象存储空间为user,并设置事务模式为读写模式,允许读取和修改数据const tx = connection.transaction('user', 'readwrite');// 获取到storeconst store = tx.objectStore('user');// deleteconst delReq = store.delete('1');delReq.onsuccess = () => {console.log('delete success');}delReq.onerror = () => {console.log('delete error'); }
}, 1000)
完整代码:
// 打开名为"test"的IndexedDB数据库,返回一个IDBOpenDBRequest对象
const db = window.indexedDB.open('test');
// 用于存储数据库连接
let connection;// 当数据库版本升级或首次创建时触发的事件
db.onupgradeneeded = (e) => {// 获取数据库连接connection = e.target.result;// 在数据库中创建一个名为'user'的对象存储空间(objectStore)// 设置'user_id'作为主键(keyPath)connection.createObjectStore('user', {keyPath: 'user_id'});
}// 成功时触发的事件
db.onsuccess = (e) => {// 获取数据库连接connection = e.target.result;console.log('connection success');
}// 失败时触发的事件
db.onerror = (e) => {console.log('connection error')
}// 模拟异步操作
setTimeout(() => {// 创建一个事务,指定要操作的对象存储空间为user,并设置事务模式为读写模式,允许读取和修改数据const tx = connection.transaction('user', 'readwrite');// 获取到storeconst store = tx.objectStore('user');// addconst addReq = store.add({user_id: '1',user_name: '前端杂货铺'})addReq.onsuccess = () => {console.log('add success');}addReq.onerror = () => {console.log('add error');}// updateconst putReq = store.put({user_id: '1',user_name: '前端杂货铺2025'})putReq.onsuccess = () => {console.log('update success');}putReq.onerror = () => {console.log('update error');}// getconst getReq = store.get('1');getReq.onsuccess = (e) => {console.log(e.target.result);}getReq.onerror = () => {console.log('get error'); }// deleteconst delReq = store.delete('1');delReq.onsuccess = () => {console.log('delete success');}delReq.onerror = () => {console.log('delete error'); }
}, 1000)
Dexie - IndexedDB的极简包装
Dexie.js 官方文档
项目地址:dexie-use-demo · GitHub
创建并启动项目。
pnpm create vite dexie-use-democd dexie-use-demo
pnpm install
pnpm run dev
安装 dexie。
pnpm install dexie
创建 src/db.ts
文件。
import Dexie, { type EntityTable } from "dexie";// 定义 Friend 接口,表示朋友对象的数据结构
interface Friend {id: number; // 朋友的唯一标识name: string; // 朋友的姓名age: number; // 朋友的年龄
}// 创建 Dexie 数据库实例,并声明 friends 表
const db = new Dexie("FriendsDatabase") as Dexie & {friends: EntityTable<Friend, "id">; // friends 表,主键为 id
};// 定义数据库的版本和表结构
db.version(1).stores({friends: "++id, name, age", // friends 表,id 为自增主键,包含 name 和 age 字段
});// 导出 Friend 类型和 db 实例,供其他模块使用
export type { Friend };
export { db }; // 导出 db 实例
创建 src/components/DBItems.vue
组件,通过 dexie 实现基本的增删改查。
<template><fieldset><legend>DB Items</legend><!-- 输入朋友姓名 --><label>Name:<input v-model="friendName" type="text" /></label><br /><!-- 输入朋友年龄 --><label>Age:<input v-model="friendAge" type="number" /></label><br /><!-- 显示当前操作状态 --><p v-if="status">当前状态:{{ status }}</p><!-- 操作按钮 --><button @click="addFriend">Add Friend</button><button @click="updateFriend">Update Friend Age By Name</button><button @click="deleteFriend">Delete Friend By Name</button><button @click="getFriend">Get Friend By Name</button><button @click="getAllFriends">Get All Friends</button><button @click="clearAllFriends">Clear All Friends</button><!-- 展示朋友列表 --><ul><liv-for="(item, index) in friends"style="list-style-type: none;display: flex;justify-content: space-between;":key="item.id"><span>Name: {{ item.name }}</span><span>Age: {{ item.age }}</span></li></ul></fieldset>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { db, Friend } from "../db";// 响应式变量
const status = ref("");
const friendName = ref("前端杂货铺");
const friendAge = ref(24);
const friends = ref<Friend[]>([]);// 添加朋友
async function addFriend(): Promise<void> {try {const id = await db.friends.add({name: friendName.value,age: friendAge.value,});status.value = `Friend ${friendName.value} successfully added. Got id ${id}`;// 重置输入friendName.value = "前端杂货铺";friendAge.value = 24;} catch (error: unknown) {status.value = `Failed to add ${friendName.value}: ${error instanceof Error ? error.message : error}`;}
}// 更新朋友信息
async function updateFriend(): Promise<void> {try {const count = await db.friends.where("name").equals(friendName.value).modify({ age: friendAge.value });status.value = count? `Friend ${friendName.value} successfully updated.`: `No friend found with name ${friendName.value}.`;} catch (error: unknown) {status.value = `Failed to update ${friendName.value}: ${error instanceof Error ? error.message : error}`;}
}// 删除朋友
async function deleteFriend(): Promise<void> {try {const count = await db.friends.where("name").equals(friendName.value).delete();status.value =count > 0? `Friend ${friendName.value} successfully deleted.`: `No friend found with name ${friendName.value}.`;} catch (error: unknown) {status.value = `Failed to delete ${friendName.value}: ${error instanceof Error ? error.message : error}`;}
}// 查询单个朋友
async function getFriend(): Promise<void> {try {const friend = await db.friends.where("name").equals(friendName.value).first();if (friend) {friends.value = [friend];status.value = `Found friend: ${friend.name}, Age: ${friend.age}`;} else {friends.value = [];status.value = `No friend found with name ${friendName.value}.`;}} catch (error: unknown) {status.value = `Failed to retrieve friend: ${error instanceof Error ? error.message : error}`;}
}// 查询所有朋友
async function getAllFriends(): Promise<void> {try {friends.value = await db.friends.toArray();status.value = `Found ${friends.value.length} friends.`;} catch (error: unknown) {status.value = `Failed to retrieve friends: ${error instanceof Error ? error.message : error}`;}
}// 清空所有朋友
async function clearAllFriends(): Promise<void> {try {await db.friends.clear();friends.value = [];status.value = "All friends cleared.";} catch (error: unknown) {status.value = `Failed to clear friends: ${error instanceof Error ? error.message : error}`;}
}
</script><style scoped>
button {margin: 5px;padding: 5px;background-color: #42b883;color: white;border: none;border-radius: 3px;cursor: pointer;
}
</style>
修改 src/App.vue
组件。
<script setup lang="ts">
import DBItems from "./components/DBItems.vue";
</script><template><h2>前端杂货铺 study IndexedDB</h2><DBItems />
</template><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
</style>
IndexedDB的对比及使用场景
对比
使用场景
-
离线 Web 应用(PWA):存储用户数据(如邮件、笔记、待办事项),即使离线也能访问和编辑,网络恢复后同步到服务器。示例:Google Docs 离线模式、Notion 本地缓存。
-
大数据存储与高效查询:存储大量结构化数据(如产品目录、用户数据),并支持 索引查询、排序、分页。示例:电商网站本地缓存商品数据,实现快速筛选。
-
文件/二进制数据存储:存储 Blob/File(如图片、音频、视频),适用于本地文件管理或编辑器。示例:图片编辑器保存未上传的草稿、PDF 阅读器缓存文档。
-
事务性操作(如购物车、支付):支持 ACID 事务,适用于需要数据一致性的场景(如库存增减、订单处理)。示例:离线购物车在结算时保证库存正确扣减。
-
替代 LocalStorage 存储复杂数据:当数据量超过 LocalStorage 限制(5-10MB),或需要存储 对象而非字符串 时,IndexedDB 是更好的选择。
总结
本篇文章我们首先认识了 IndexedDB 主要用于在客户端存储大量的结构化数据。随后我们联系了基本的 增删改查 的使用。最后我们使用了其对应的第三方库 Dexie 进行了增删改查操作。以上内容都比较简单,属于入门内容,如果大家对这方面感兴趣可以继续深究下去,有不少比较底层的 API 供我们学习使用。
学不可以已,我们下篇文章见~
好啦,本篇文章到这里就要和大家说再见啦,祝你这篇文章阅读愉快,你下篇文章的阅读愉快留着我下篇文章再祝!
参考资料:
- MDN · IndexedDB:MDN_IndexedDB
- 百度 · 百科
- DeepSeek:DeepSeek
- VS Code · Copilot
- Dexie:Dexie官网