目录
一、 IndexedDB 介绍
二、 IndexedDB的特点
三、 使用场景
四、在谷歌开发者工具中使用IndexedDB
五、代码实战
5.1 简单增删改查示例
5.2 存储图片和现实图片示例
六、总结
作为一名资深的前端开发者,试想一下这个场景:凌晨1点,咖啡见底,你正在为一个复杂的表单草稿丢失而抓狂。当用户关掉浏览器,表单数据就没了?或者,你雄心勃勃地想做一款离线也能流畅操作的PWA应用,数据存哪?
5MB不够用?
localStorage
太小还不安全?这时候,浏览器角落里那个名字有点唬人的家伙——IndexedDB,是否犹如灵感般出现在你的脑海中!
Cookies
没错今天的要分享的主角就是浏览器开发工具中的IndexDB技术,感兴趣的前端朋友可以来了解一下!
一、 IndexedDB 介绍
IndexedDB英文全称为Indexed Database API,简单来说IndexedDB 是浏览器内置的一个事务型、NoSQL 数据库系统。 它让你能在用户的浏览器里持久化存储大量结构化数据(对象、文件/blob等),并且支持高性能的索引查询。目前主流的浏览器都支持IndexedDB,比如:Chrome,Firefox,Opera,Safari完全支持IndexDB,IE10/IE11和Edge部分支持。
核心概念:
数据库 (Database): 一个独立命名的存储容器。每个域名下可以创建多个DB。
对象仓库 (Object Store): 相当于数据库里的“表”,用于存储特定类型的JavaScript对象(键值对集合)。每个对象有一个唯一键。
索引 (Index): 在对象仓库上建立的,用于快速按非主键字段查询数据的结构。
事务 (Transaction): 所有读写操作都必须在事务中进行,保证操作的原子性和一致性(要么全成功,要么全失败)。
游标 (Cursor): 用于遍历对象仓库或索引中的大量数据。
键 (Key): 可以是数字、字符串、日期、数组,甚至是二进制数据。可以是对象本身的属性(内联键),也可以单独指定(外联键)。
二、 IndexedDB的特点
海量存储: 浏览器通常允许单个IndexedDB数据库占用大量磁盘空间。存个几MB、几十MB甚至更大的数据(如图片缓存、文档草稿)完全没问题。
结构化、支持索引: 不像 localStorage 只能存字符串。IndexedDB 存的是 JavaScript 对象。更重要的是,你可以创建索引,实现高效查询(比如按用户名、按时间戳快速查找)。
异步操作: 所有API都是异步的(基于事件或Promise),不会阻塞主线程,保证了页面流畅性。
事务支持: 保证了数据操作的完整性和一致性。尤其在复杂操作(先读A再写B再更新C)时非常关键。
同源策略: 和 localStorage 一样,遵守同源策略。不同域无法访问。
支持二进制数据 (Blob/File): 可以直接存储图片、音频、文件片段等二进制数据,这对于离线图片预览、文档缓存等场景非常有用。
三、 使用场景
离线优先应用 (PWA): 这是 IndexedDB 的主要战场!在用户离线时,将应用数据(用户配置、文章草稿、消息记录、商品列表、图片资源等)完整保存在本地。网络恢复后再同步到服务器。提供无缝的离线体验。
富文本编辑器 / 复杂表单的自动保存: 用户输入内容频繁地、静默地保存到 IndexedDB。即使浏览器崩溃、页面意外关闭,也能恢复大部分内容。比频繁请求服务器保存高效得多。
大型应用数据的客户端缓存: 对于数据量较大、更新频率不高(或增量更新)的数据(如城市列表、商品分类、用户历史记录、配置信息),首次加载后存入 IndexedDB。后续访问优先从本地读取,极大提升加载速度和用户体验,减少服务器压力。
客户端日志/分析数据持久化: 收集的用户行为日志、错误报告等,可以先批量存储在 IndexedDB 中,待网络良好或有足够数量时再统一上报服务器,避免因网络波动导致数据丢失。
文件/资源的本地缓存: 如图片库、文档查看器。用户访问过的图片或文档可以缓存在 IndexedDB 中,下次访问无需下载,实现秒开。
游戏状态保存: 网页游戏的关卡进度、玩家属性、装备信息等,可以方便地保存在 IndexedDB 中。
四、在谷歌开发者工具中使用IndexedDB
这里使用谷歌浏览器开发者工具切换为中文界面来演示
找到它: 打开 谷歌开发者工具 (
/
F12
/
Cmd+Opt+I
) -> 切换到 应用 面板 -> 左侧菜单找到 存储 下的
Ctrl+Shift+I
。你会看到当前页面域名下的所有 IndexedDB 数据库。
IndexedDB
查看数据库结构:
点击数据库名展开,能看到它包含的
。
Object Stores
点击
名字,右侧面板会展示其存储的数据列表(键值对)。
Object Store
注意看
和
Key Path
,这决定了数据的组织和查询方式。
Indexes
查看数据:
在右侧数据列表里,可以直接看到存储的 JavaScript 对象。
右键数据行,可以执行
操作。
Delete
筛选与搜索:
在
数据视图的顶部,有筛选输入框。可以根据键 (
Object Store
) 或值 (
Key
) 进行过滤(支持部分匹配)。
Value
对于大型数据集,筛选功能非常实用。
清空与删除:
清空 Object Store: 右键点击某个
->
Object Store
。瞬间清空这个“表”的所有数据。
Clear object store
删除 Object Store / Index: 右键点击 ->
。注意:这会删除结构定义和数据!
Delete
删除整个数据库: 直接在 应用-> 删除数据库 里勾选
进行清除。
IndexedDB
调试事务与错误:
在 控制台面板中,你的代码操作 IndexedDB 时产生的错误(权限问题、版本冲突、约束错误等)会清晰地打印出来。结合开发者工具中的源代码面板断点调试,定位问题效率极高。
高级)性能分析: 在 性能标签面板录制操作时,可以看到 IndexedDB 读写操作的耗时,帮助优化数据库设计(如索引是否有效)。
使用技巧:
版本升级: 修改数据库结构(增删 Object Store/Index)需要升级
。DevTools 里能看到当前版本号。升级逻辑要在
db.version
事件里写。务必在DevTools里测试好升级逻辑! 否则线上用户数据可能出问题。
onupgradeneeded
异步地狱: 原生 API 是基于事件的回调,写起来容易嵌套。强烈推荐使用封装库:
,
Dexie.js
(Jake Archibald 的轻量封装) 等。它们提供 Promise API,代码清爽几十倍!在 DevTools 里调试时,这些库操作的数据同样可见。
idb
存储限制与回收: 浏览器在磁盘空间不足时可能清除 IndexedDB 数据)。重要数据要有备份或同步机制。
DevTools 是上帝视角: 你在 DevTools 里做的删除操作,是“超能力”,不受代码事务限制。线上环境用户可没这能力! 所以你的代码逻辑(增删改查、事务处理、错误捕获)才是王道,开发者工具主要是辅助验证和调试的。
五、代码实战
5.1 简单增删改查示例
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>IndexedDB 增删改查示例</title></head><body> <button id="addBtn">添加数据</button> <button id="deleteBtn">删除数据</button> <button id="updateBtn">更新数据</button> <button id="queryBtn">查询数据</button> <script> // 打开或创建数据库 const request = indexedDB.open('myDatabase', 1); // 数据库打开失败 request.onerror = function (event) { console.error('数据库打开失败:', event.target.errorCode); }; // 数据库打开成功 request.onsuccess = function (event) { const db = event.target.result; console.log('数据库打开成功'); // 添加数据 document.getElementById('addBtn').addEventListener('click', function () { const transaction = db.transaction(['myObjectStore'], 'readwrite'); const objectStore = transaction.objectStore('myObjectStore'); const data = { id: 1, name: '李强', age: 30 }; const addRequest = objectStore.add(data); addRequest.onsuccess = function () { console.log('数据添加成功'); }; addRequest.onerror = function (event) { console.error('数据添加失败:', event.target.errorCode); }; }); // 删除数据 document.getElementById('deleteBtn').addEventListener('click', function () { const transaction = db.transaction(['myObjectStore'], 'readwrite'); const objectStore = transaction.objectStore('myObjectStore'); const deleteRequest = objectStore.delete(1); deleteRequest.onsuccess = function () { console.log('数据删除成功'); }; deleteRequest.onerror = function (event) { console.error('数据删除失败:', event.target.errorCode); }; }); // 更新数据 document.getElementById('updateBtn').addEventListener('click', function () { const transaction = db.transaction(['myObjectStore'], 'readwrite'); const objectStore = transaction.objectStore('myObjectStore'); const data = { id: 1, name: 'Jane', age: 30 }; const putRequest = objectStore.put(data); putRequest.onsuccess = function () { console.log('数据更新成功'); }; putRequest.onerror = function (event) { console.error('数据更新失败:', event.target.errorCode); }; }); // 查询数据 document.getElementById('queryBtn').addEventListener('click', function () { const transaction = db.transaction(['myObjectStore'], 'readonly'); const objectStore = transaction.objectStore('myObjectStore'); const getRequest = objectStore.get(1); getRequest.onsuccess = function (event) { const result = event.target.result; if (result) { console.log('查询结果:', result); } else { console.log('未找到数据'); } }; getRequest.onerror = function (event) { console.error('数据查询失败:', event.target.errorCode); }; }); }; // 数据库版本更新时创建对象仓库和索引 request.onupgradeneeded = function (event) { const db = event.target.result; if (!db.objectStoreNames.contains('myObjectStore')) { const objectStore = db.createObjectStore('myObjectStore', { keyPath: 'id' }); objectStore.createIndex('name', 'name', { unique: false }); objectStore.createIndex('age', 'age', { unique: false }); } }; </script></body></html>
5.2 存储图片和现实图片示例
<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>IndexedDB 图片存储(原生JS)</title> <style> .preview { border: 1px solid #ddd; min-height: 300px; margin: 20px 0; } button { padding: 8px 12px; margin-right: 10px; } </style></head><body> <input type="file" id="fileInput" accept="image/*"> <button id="storeBtn">存储图片</button> <button id="loadBtn">加载最新图片</button> <div class="preview"> <img id="dbImage" style="max-width: 100%; display: none;"> </div> <div id="status">就绪</div> <script> // 核心配置 const DB_NAME = "ImageDB"; const STORE_NAME = "images"; const DB_VERSION = 1; let db = null; // 状态更新 function updateStatus(message) { document.getElementById('status').textContent = message; } // 1. 初始化数据库(Promise封装) function initDB() { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); // 数据库结构升级 request.onupgradeneeded = (event) => { const db = event.target.result; // 关键修复:确保对象存储存在 if (!db.objectStoreNames.contains(STORE_NAME)) { db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true }); updateStatus("对象存储创建成功"); } }; request.onsuccess = (event) => { db = event.target.result; updateStatus("数据库已连接"); resolve(db); }; request.onerror = (event) => { updateStatus(`数据库错误: ${event.target.error}`); reject(event.target.error); }; }); } // 2. 存储图片到数据库 async function storeImage() { const file = document.getElementById('fileInput').files[0]; if (!file) { updateStatus("请选择图片文件"); return; } try { // 读取为ArrayBuffer(兼容性更好) const arrayBuffer = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = () => reject(reader.error); reader.readAsArrayBuffer(file); }); const transaction = db.transaction([STORE_NAME], 'readwrite'); const store = transaction.objectStore(STORE_NAME); // 构建元数据对象 const imageData = { name: file.name, type: file.type, size: file.size, timestamp: new Date(), imageData: arrayBuffer }; // 存储操作 const request = store.add(imageData); request.onsuccess = () => { updateStatus(`图片存储成功 ID: ${request.result}`); }; request.onerror = (event) => { updateStatus(`存储失败: ${event.target.error}`); }; } catch (error) { updateStatus(`错误: ${error.message}`); } } // 3. 从数据库加载最新图片 async function loadLatestImage() { const transaction = db.transaction([STORE_NAME], 'readonly'); const store = transaction.objectStore(STORE_NAME); const request = store.openCursor(null, 'prev'); // 反向遍历取最新 request.onsuccess = (event) => { const cursor = event.target.result; if (cursor) { displayImage(cursor.value); } else { updateStatus("数据库中没有图片"); } }; request.onerror = (event) => { updateStatus(`加载失败: ${event.target.error}`); }; } // 4. 显示图片 function displayImage(imageRecord) { const blob = new Blob([imageRecord.imageData], { type: imageRecord.type }); const imageUrl = URL.createObjectURL(blob); const imgElement = document.getElementById('dbImage'); imgElement.src = imageUrl; imgElement.style.display = 'block'; imgElement.alt = `已加载: ${imageRecord.name}`; // 释放内存 imgElement.onload = () => URL.revokeObjectURL(imageUrl); updateStatus(`已加载: ${imageRecord.name} (${formatBytes(imageRecord.size)})`); } // 辅助函数:格式化文件大小 function formatBytes(bytes) { const units = ['B', 'KB', 'MB', 'GB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(2)} ${units[unitIndex]}`; } // 5. 初始化应用 window.addEventListener('DOMContentLoaded', async () => { updateStatus("正在初始化数据库..."); try { await initDB(); // 绑定事件 document.getElementById('storeBtn').addEventListener('click', storeImage); document.getElementById('loadBtn').addEventListener('click', loadLatestImage); updateStatus("就绪:选择图片后点击存储"); } catch (error) { updateStatus(`初始化失败: ${error.message}`); } }); </script></body></html>
查看存储效果
加载图片效果
六、总结
IndexedDB它是构建现代、高性能、离线友好型 Web 应用的基石之一。对于前端开发工程师来说属于必备技能。希望本篇文章能对大家了解IndexedDB技术提供一些帮助!
互动时间:
灵魂拷问: 你负责的项目里,哪些数据最适合迁移到 IndexedDB?是用户草稿?配置项?还是缓存的大列表?
踩坑分享: 你在使用 IndexedDB 或者用 DevTools 调试它时,遇到过什么印象深刻的“坑”?说出来让大家避避雷!(比如诡异的版本升级失败?)
第三方库安利: 你更喜欢用哪个 IndexedDB 封装库?
?
Dexie.js
?还是其他?为啥?
idb