

作为一名资深的前端开发者,试想一下这个场景:凌晨1点,咖啡见底,你正在为一个复杂的表单草稿丢失而抓狂。当用户关掉浏览器,表单数据就没了?或者,你雄心勃勃地想做一款离线也能流畅操作的PWA应用,数据存哪?localStorage 5MB不够用?Cookies 太小还不安全?这时候,浏览器角落里那个名字有点唬人的家伙——IndexedDB,是否犹如灵感般出现在你的脑海中!
没错今天的要分享的主角就是浏览器开发工具中的IndexDB技术,感兴趣的前端朋友可以来了解一下!
IndexedDB英文全称为Indexed Database API,简单来说IndexedDB 是浏览器内置的一个事务型、NoSQL 数据库系统。 它让你能在用户的浏览器里持久化存储大量结构化数据(对象、文件/blob等),并且支持高性能的索引查询。目前主流的浏览器都支持IndexedDB,比如:Chrome,Firefox,Opera,Safari完全支持IndexDB,IE10/IE11和Edge部分支持。
核心概念:


海量存储: 浏览器通常允许单个IndexedDB数据库占用大量磁盘空间。存个几MB、几十MB甚至更大的数据(如图片缓存、文档草稿)完全没问题。
结构化、支持索引: 不像 localStorage 只能存字符串。IndexedDB 存的是 JavaScript 对象。更重要的是,你可以创建索引,实现高效查询(比如按用户名、按时间戳快速查找)。
异步操作: 所有API都是异步的(基于事件或Promise),不会阻塞主线程,保证了页面流畅性。
事务支持: 保证了数据操作的完整性和一致性。尤其在复杂操作(先读A再写B再更新C)时非常关键。
同源策略: 和 localStorage 一样,遵守同源策略。不同域无法访问。
支持二进制数据 (Blob/File): 可以直接存储图片、音频、文件片段等二进制数据,这对于离线图片预览、文档缓存等场景非常有用。

离线优先应用 (PWA): 这是 IndexedDB 的主要战场!在用户离线时,将应用数据(用户配置、文章草稿、消息记录、商品列表、图片资源等)完整保存在本地。网络恢复后再同步到服务器。提供无缝的离线体验。
富文本编辑器 / 复杂表单的自动保存: 用户输入内容频繁地、静默地保存到 IndexedDB。即使浏览器崩溃、页面意外关闭,也能恢复大部分内容。比频繁请求服务器保存高效得多。
大型应用数据的客户端缓存: 对于数据量较大、更新频率不高(或增量更新)的数据(如城市列表、商品分类、用户历史记录、配置信息),首次加载后存入 IndexedDB。后续访问优先从本地读取,极大提升加载速度和用户体验,减少服务器压力。
客户端日志/分析数据持久化: 收集的用户行为日志、错误报告等,可以先批量存储在 IndexedDB 中,待网络良好或有足够数量时再统一上报服务器,避免因网络波动导致数据丢失。
文件/资源的本地缓存: 如图片库、文档查看器。用户访问过的图片或文档可以缓存在 IndexedDB 中,下次访问无需下载,实现秒开。
游戏状态保存: 网页游戏的关卡进度、玩家属性、装备信息等,可以方便地保存在 IndexedDB 中。
这里使用谷歌浏览器开发者工具切换为中文界面来演示
找到它: 打开 谷歌开发者工具 (F12 / Cmd+Opt+I / Ctrl+Shift+I) -> 切换到 应用 面板 -> 左侧菜单找到 存储 下的 IndexedDB。你会看到当前页面域名下的所有 IndexedDB 数据库。

查看数据库结构:

Object Stores。Object Store 名字,右侧面板会展示其存储的数据列表(键值对)。Key Path 和 Indexes,这决定了数据的组织和查询方式。查看数据:

Delete 操作。
筛选与搜索:

Object Store 数据视图的顶部,有筛选输入框。可以根据键 (Key) 或值 (Value) 进行过滤(支持部分匹配)。清空与删除:

Object Store -> Clear object store。瞬间清空这个“表”的所有数据。Delete。注意:这会删除结构定义和数据!IndexedDB 进行清除。
调试事务与错误:
高级)性能分析: 在 性能标签面板录制操作时,可以看到 IndexedDB 读写操作的耗时,帮助优化数据库设计(如索引是否有效)。
使用技巧:
版本升级: 修改数据库结构(增删 Object Store/Index)需要升级 db.version。DevTools 里能看到当前版本号。升级逻辑要在 onupgradeneeded 事件里写。务必在DevTools里测试好升级逻辑! 否则线上用户数据可能出问题。
异步地狱: 原生 API 是基于事件的回调,写起来容易嵌套。强烈推荐使用封装库:Dexie.js, idb (Jake Archibald 的轻量封装) 等。它们提供 Promise API,代码清爽几十倍!在 DevTools 里调试时,这些库操作的数据同样可见。
存储限制与回收: 浏览器在磁盘空间不足时可能清除 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?还是其他?为啥?
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。