Indexed DB
0x01 概述
(1)简介
- IndexedDB 是一个事务型数据库系统,类似于基于 SQL 的 RDBMS(关系型数据库系统)
- IndexedDB 是一个基于 JavaScript 的面向对象数据库
- IndexedDB 允许存储和检索用键索引的对象,可以存储结构化克隆算法支持的任何对象
- 其他本地缓存方法参考《前端性能优化实践方向与方法 | 博客园-SRIGT》
- IndexedDB 详细介绍参考《IndexedDB_API | MDN》
- 下述操作代码改进自《前端本地存储数据库IndexedDB完整教程 | 知乎-会飞的猪》
- 使用场景主要用于存储大量数据(大于 250MB) ,如:
- 数据可视化界面
- 即时聊天
- 其他缓存方式容量不足
- IndexedDB 执行的操作是异步执行的,以免阻塞应用程序
(2)名词
- 仓库(
objectStore):相当于 MySQL 的表 - 索引(
index):用于加快查找速率 - 游标(
cursor):相当于指针,遍历每行的数据并返回- IndexedDB 仅通过主键、索引、游标的方式查询数据
- 事务(
transaction):用于确保数据一致性,当操作失败时可以回滚
(3)环境准备
- 采用原生 HTML+JavaScript 开发,理论上用记事本就可以完成,推荐采用 VSCode+Live Preview 插件
- 下述代码在 Google Chrome 136.0.7103.93 测试通过
-
新建 script.js,用于封装 IndexedDB 相关方法
-
同目录下新建 index.html,用于可视化操作
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>IndexedDB</title> <script src="./script.js"></script> </head> <body></body> <script></script> </html>
0x02 创建与连接
(1)数据库
// 浏览器兼容性判断
const currentIndexedDB =
window.indexedDB || // IE9+
window.mozIndexedDB || // Firefox
window.webkitIndexedDB || // Chrome
window.msIndexedDB; // Safari
/**
* 创建或连接数据库
* @param {object} dbName 数据库名称
* @param {number} version 数据库版本
* @param {function} upgradeCallback 数据库更新回调
* @returns {Promise} 数据库实例
*/
function connect(dbName, version = 1, upgradeCallback = () => {}) {
return new Promise((resolve, reject) => {
if (!dbName) reject("请传入数据库名称");
// 打开数据库,没有则创建
const request = currentIndexedDB.open(dbName, version);
let db;
// 数据库连接成功回调
request.onsuccess = (event) => {
db = event.target.result;
console.log("数据库连接成功");
resolve(db);
};
// 数据库连接失败回调
request.onerror = (event) => {
console.error("数据库连接失败");
reject(event.target.error);
};
// 数据库更新回调
request.onupgradeneeded = (event) => {
db = event.target.result;
console.log("数据库更新");
resolve(upgradeCallback(db));
};
});
}
当版本(version)变化时触发
onupgradeneeded
(2)仓库与索引
-
仓库与索引可以在
request.onupgradeneeded回调函数中创建 -
举例:创建用户仓库及其索引
-
script.js
/** * 创建仓库 * @param {IDBDatabase} db 数据库实例 * @param {string} tableName 仓库名 * @param {object} options 仓库结构 * @param {array} index 索引字符串数组 * @returns {Promise} 仓库实例 */ function createObjectStore(db, storeName, options, index = []) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!options) reject("请传入仓库结构"); // 创建仓库 const objectStore = db.createObjectStore(storeName, options); // 创建索引 index.forEach((item) => objectStore.createIndex(item, item, { unique: false }) ); // 仓库创建成功回调 objectStore.oncomplete = (event) => { console.log("仓库创建成功"); resolve(objectStore); }; // 仓库创建失败回调 objectStore.onerror = (event) => { console.error("仓库创建失败"); reject(event.target.error); }; }); } -
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>IndexedDB</title> <script src="./script.js"></script> </head> <body> <button onclick="init()">初始化数据库</button> <script> const DATABASE_NAME = "myDB", USERS_STORE = "users"; let db = null; // 初始化数据库 async function init() { db = await connect(DATABASE_NAME, 1, async (database) => { await createObjectStore(database, USERS_STORE, { keyPath: "id" }, [ "name", "email", ]); }); } </script> </body> </html>
-
0x03 增删改查
(1)新增数据
-
script.js
/** * 新增数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {object} data 待新增数据 * @returns {Promise} 新增数据结果 */ function insert(db, storeName, data) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!data) reject("请传入数据"); const request = db .transaction(storeName, "readwrite") // 创建事务,读写 .objectStore(storeName) // 获取仓库 .add(data); // 添加数据 request.onsuccess = () => { console.log("数据添加成功"); resolve(); }; request.onerror = (event) => { console.error("数据添加失败"); reject(event.target.error); }; }); } -
index.html
<!DOCTYPE html> <html> <!-- ... --> <body> <button onclick="init()">初始化数据库</button> <fieldset> <legend>新增数据</legend> <form> <label>姓名:<input type="text" name="name" /></label> <label>邮箱:<input type="text" name="email" /></label> <input type="button" value="保存" onclick="insertSubmit(event)" /> </form> </fieldset> <script> const DATABASE_NAME = "myDB", USERS_STORE = "users"; let db = null; // 生成 uuid function uuidv4() { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { var r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8; return v.toString(16); }); } // 初始化数据库 async function init() { // ... } // 保存表单数据到数据库 async function insertSubmit(event) { var form = event.target.form; await insert(db, USERS_STORE, { id: uuidv4(), name: form.name.value, email: form.email.value, }); } </script> </body> </html>
(2)查询数据
-
根据主键查询
/** * 根据主键获取数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {string} key 主键 * @returns {Promise} 获取数据结果 */ function queryByPrimaryKey(db, storeName, key) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!key) reject("请传入主键"); const request = db .transaction(storeName, "readonly") // 创建事务,只读 .objectStore(storeName) // 获取仓库 .get(key); request.onsuccess = (event) => { console.log("数据获取成功"); resolve(event.target.result); }; request.onerror = (event) => { console.error("数据获取失败"); reject(event.target.error); }; }); } -
根据游标查询
/** * 根据游标获取数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @returns {Promise} 获取数据结果 */ function queryByCursor(db, storeName) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); // 存储结果 const result = []; const request = db .transaction(storeName, "readonly") // 创建事务,只读 .objectStore(storeName) // 获取仓库 .openCursor(); // 获取游标 request.onsuccess = (event) => { console.log("数据获取成功"); let cursor = event.target.result; if (cursor) { result.push(cursor.value); cursor.continue(); } else resolve(result); }; request.onerror = (event) => { console.error("数据获取失败"); reject(event.target.error); }; }); } -
根据索引查询
/** * 根据索引获取数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {{name, value}} indexObject 索引对象 * @returns {Promise} 获取数据结果 */ function queryByIndex(db, storeName, indexObject) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!indexObject) reject("请传入索引对象"); else if (!indexObject.name) reject("请传入索引名称"); else if (!indexObject.value) reject("请传入索引值"); const request = db .transaction(storeName, "readonly") // 创建事务,只读 .objectStore(storeName) // 获取仓库 .index(indexObject.name) // 获取索引 .get(indexObject.value); // 获取数据 request.onsuccess = (event) => { console.log("数据获取成功"); resolve(event.target.result); }; request.onerror = (event) => { console.error("数据获取失败"); reject(event.target.error); }; }); } -
根据索引和游标查询
/** * 根据索引和游标获取数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {{name, value}} indexObject 索引对象 * @returns {Promise} 获取数据结果 */ function queryByIndexAndCursor(db, storeName, indexObject) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!indexObject) reject("请传入索引对象"); else if (!indexObject.name) reject("请传入索引名称"); else if (!indexObject.value) reject("请传入索引值"); // 存储结果 const result = []; const request = db .transaction(storeName, "readonly") // 创建事务,只读 .objectStore(storeName) // 获取仓库 .index(indexObject.name) // 获取索引 .openCursor(IDBKeyRange.only(indexObject.value)); // 获取游标 request.onsuccess = (event) => { console.log("数据获取成功"); let cursor = event.target.result; if (cursor) { result.push(cursor.value); cursor.continue(); } else resolve(result); }; request.onerror = (event) => { console.error("数据获取失败"); reject(event.target.error); }; }); } -
根据索引和游标并分页查询
/** * 根据索引和游标并分页获取数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {{name, value}} indexObject 索引对象 * @param {{number, size}} pageObject 分页对象 * @returns {Promise} 获取数据结果 */ function queryByIndexAndCursorAndPagination( db, storeName, indexObject, pageObject ) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!indexObject) reject("请传入索引对象"); else if (!indexObject.name) reject("请传入索引名称"); else if (!indexObject.value) reject("请传入索引值"); if (!pageObject) reject("请传入分页对象"); else if (!pageObject.number) reject("请传入页码"); else if (pageObject.number < 1) reject("页码不能小于 1"); else if (!pageObject.size) reject("请传入页大小"); else if (pageObject.size < 1) reject("页大小不能小于 1"); // 存储结果 const result = []; let counter = 0, // 计数器 advanced = true; // 是否已跳过指定页码 const request = db .transaction(storeName, "readonly") // 创建事务,只读 .objectStore(storeName) // 获取仓库 .index(indexObject.name) // 获取索引 .openCursor(IDBKeyRange.only(indexObject.value)); // 获取游标 request.onsuccess = (event) => { console.log("数据获取成功"); let cursor = event.target.result; // 跳过指定页码 if (pageObject.number > 1 && advanced) { advanced = false; cursor.advance((pageObject.number - 1) * pageObject.size); return; } if (cursor) { result.push(cursor.value); counter++; if (counter < pageObject.size) cursor.continue(); // 继续获取下一条数据 else resolve(result); } else resolve(result); }; request.onerror = (event) => { console.error("数据获取失败"); reject(event.target.error); }; }); } -
index.html
<!DOCTYPE html> <html> <!-- ... --> <body> <!-- ... --> <fieldset> <legend>查询数据</legend> <fieldset> <legend>根据主键查询</legend> <form> <label>主键:<input type="text" name="pk" /></label> <input type="button" value="查询" onclick="querySubmit(event, 'pk')" /> </form> </fieldset> <fieldset> <legend>根据游标查询</legend> <form> <input type="button" value="查询所有" onclick="querySubmit(event, 'cursor')" /> </form> </fieldset> <fieldset> <legend>根据索引查询</legend> <form> <label>姓名:<input type="text" name="name" /></label> <input type="button" value="查询" onclick="querySubmit(event, 'index')" /> </form> </fieldset> <fieldset> <legend>根据索引和游标查询</legend> <form> <label>姓名:<input type="text" name="name" /></label> <input type="button" value="查询" onclick="querySubmit(event, 'index-cursor')" /> </form> </fieldset> <fieldset> <legend>根据索引和游标并分页查询</legend> <form> <label> 邮箱: <input type="text" name="email" /> </label> <br /> <label> 页码: <input type="number" min="1" name="page-number" /> </label> <label> 每页数量:<input type="number" min="1" name="page-size" /> </label> <input type="button" value="查询" onclick="querySubmit(event, 'index-cursor-pagination')" /> </form> </fieldset> <fieldset> <legend>查询结果</legend> <ul id="result"></ul> </fieldset> </fieldset> <script> // ... // 查询表单数据 async function querySubmit(event, type) { var form = event.target.form; var result = document.getElementById("result"); result.innerHTML = ""; var data = []; switch (type) { case "pk": data = [await queryByPrimaryKey(db, USERS_STORE, form["pk"].value)]; break; case "cursor": data = await queryByCursor(db, USERS_STORE); break; case "index": data = [ await queryByIndex(db, USERS_STORE, { name: "name", value: form["name"].value, }), ]; break; case "index-cursor": data = await queryByIndexAndCursor(db, USERS_STORE, { name: "name", value: form["name"].value, }); break; case "index-cursor-pagination": data = await queryByIndexAndCursorAndPagination( db, USERS_STORE, { name: "email", value: form["email"].value, }, { number: parseInt(form["page-number"].value), size: parseInt(form["page-size"].value), } ); break; default: alert("未知查询类型"); break; } // 创建结果列表 const fragment = document.createDocumentFragment(); if (data.length > 0) data.forEach((item) => { const li = document.createElement("li"); li.textContent = item.name + " " + item.email; fragment.appendChild(li); }); result.appendChild(fragment); } </script> </body> </html>
(3)更新数据
-
script.js
/** * 修改数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {object} data 待修改数据 * @returns {Promise} 修改数据结果 */ function update(db, storeName, data) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!data) reject("请传入数据"); else if (!data.id) reject("请传入数据 ID"); const request = db .transaction(storeName, "readwrite") // 创建事务,读写 .objectStore(storeName) // 获取仓库 .put(data); // 修改数据 request.onsuccess = () => { console.log("数据修改成功"); resolve(); }; request.onerror = (event) => { console.error("数据修改失败"); reject(event.target.error); }; }); } -
index.html
<!DOCTYPE html> <html> <!-- ... --> <body> <!-- ... --> <fieldset> <legend>修改数据</legend> <form> <label>主键:<input type="text" name="pk" /></label> <label>姓名:<input type="text" name="name" /></label> <label>邮箱:<input type="text" name="email" /></label> <input type="button" value="保存" onclick="updateSubmit(event)" /> </form> </fieldset> </fieldset> <script> // ... // 修改表单数据 async function updateSubmit(event) { var form = event.target.form; await update(db, USERS_STORE, { id: form.pk.value, name: form.name.value, email: form.email.value, }); } </script> </body> </html>
(4)删除数据
-
根据主键删除
/** * 根据主键删除数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {string} key 主键 * @returns {Promise} 获取数据结果 */ function deleteByPrimaryKey(db, storeName, key) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!key) reject("请传入主键"); const request = db .transaction(storeName, "readwrite") // 创建事务,读写 .objectStore(storeName) // 获取仓库 .delete(key); // 删除数据 request.onsuccess = (event) => { console.log("数据删除成功"); resolve(event.target.result); }; request.onerror = (event) => { console.error("数据删除失败"); reject(event.target.error); }; }); } -
根据索引和游标删除
/** * 根据索引和游标删除数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {{name, value}} indexObject 索引对象 * @returns {Promise} 删除数据结果 */ function deleteByIndexAndCursor(db, storeName, indexObject) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!indexObject) reject("请传入索引对象"); else if (!indexObject.name) reject("请传入索引名称"); else if (!indexObject.value) reject("请传入索引值"); const request = db .transaction(storeName, "readwrite") // 创建事务,读写 .objectStore(storeName) // 获取仓库 .index(indexObject.name) // 获取索引 .openCursor(IDBKeyRange.only(indexObject.value)); // 获取游标 request.onsuccess = (event) => { let cursor = event.target.result; if (cursor) { const deleteRequest = cursor.delete(); deleteRequest.onsuccess = () => { console.log("数据删除成功"); }; deleteRequest.onerror = () => { console.error("数据删除失败"); reject(deleteRequest.error); }; cursor.continue(); } else resolve(); }; request.onerror = (event) => { console.error("数据删除失败"); reject(event.target.error); }; }); } -
index.html
<!DOCTYPE html> <html> <!-- ... --> <body> <!-- ... --> <fieldset> <legend>删除数据</legend> <fieldset> <legend>根据主键删除</legend> <form> <label>主键:<input type="text" name="pk" /></label> <input type="button" value="删除" onclick="deleteSubmit(event, 'pk')" /> </form> </fieldset> <fieldset> <legend>根据索引和游标删除</legend> <form> <label>姓名:<input type="text" name="name" /></label> <input type="button" value="删除" onclick="deleteSubmit(event, 'index-cursor')" /> </form> </fieldset> </fieldset> <script> // ... // 删除表单数据 async function deleteSubmit(event, type) { var form = event.target.form; switch (type) { case "pk": await deleteByPrimaryKey(db, USERS_STORE, form["pk"].value); break; case "index-cursor": await deleteByIndexAndCursor(db, USERS_STORE, { name: "name", value: form["name"].value, }); break; default: alert("未知删除类型"); break; } } </script> </body> </html>
0x04 删库与断开
(1)删除仓库
-
script.js
/** * 删除仓库 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @returns {Promise} 删除仓库结果 */ function deleteObjectStore(db, storeName) { return new Promise((resolve, reject) => { if (!storeName) reject("仓库名称不能为空"); if (db.objectStoreNames.contains(storeName)) { const deleteRequest = db.deleteObjectStore(storeName); deleteRequest.onsuccess = () => { console.log("仓库删除成功"); resolve(); }; deleteRequest.onerror = (event) => { console.error("仓库删除失败"); reject(event.target.error); }; } else { reject("仓库不存在"); } }); } -
index.html
<!DOCTYPE html> <html> <!-- ... --> <body> <!-- ... --> <button onclick="deleteDatabaseSubmit()">删除仓库</button> <script> const DATABASE_NAME = "myDB", USERS_STORE = "users"; let db = null; // ... // 删除仓库 async function deleteObjectStoreSubmit() { db = await connect(DATABASE_NAME, 2, async (database) => { await deleteObjectStore(database, USERS_STORE); }); } </script> </body> </html>
(2)删除数据库
-
script.js
/** * 删除数据库 * @param {string} dbName 数据库名称 * @returns {Promise} 删除数据库结果 */ function deleteDatabase(dbName) { return new Promise((resolve, reject) => { if (!dbName) reject("请输入数据库名称"); if (!currentIndexedDB) reject("请先初始化数据库"); const request = currentIndexedDB.deleteDatabase(dbName); request.onsuccess = () => { console.log("数据库删除成功"); resolve(); }; request.onerror = (event) => { console.error("数据库删除失败"); reject(event.target.error); }; }); } -
index.html
<!DOCTYPE html> <html> <!-- ... --> <body> <!-- ... --> <button onclick="deleteDatabaseSubmit()">删除数据库</button> <script> const DATABASE_NAME = "myDB", USERS_STORE = "users"; let db = null; // ... // 删除数据库 async function deleteDatabaseSubmit() { try { await deleteDatabase(DATABASE_NAME); db = null; } catch (error) {} } </script> </body> </html>
(3)断开连接
-
script.js
/** * 关闭数据库连接 * @param {IDBDatabase} db 数据库对象 */ function disconnect(db) { if (!db) return; db.close(); console.log("数据库已断开连接"); } -
index.html
<!DOCTYPE html> <html> <!-- ... --> <body> <!-- ... --> <button onclick="disconnectSubmit()">断开连接</button> <script> const USERS_STORE = "users"; let db = null; // ... // 断开连接 function disconnectSubmit() { disconnect(); db = null; } </script> </body> </html>
完整代码
-
script.js
// 浏览器兼容性判断 const currentIndexedDB = window.indexedDB || // IE9+ window.mozIndexedDB || // Firefox window.webkitIndexedDB || // Chrome window.msIndexedDB; // Safari /** * 创建或连接数据库 * @param {string} dbName 数据库名称 * @param {number} version 数据库版本 * @param {function} upgradeCallback 数据库更新回调 * @returns {Promise} 数据库实例 */ function connect(dbName, version = 1, upgradeCallback = () => {}) { return new Promise((resolve, reject) => { if (!dbName) reject("请传入数据库名称"); // 打开数据库,没有则创建 const request = currentIndexedDB.open(dbName, version); let db; // 数据库连接成功回调 request.onsuccess = (event) => { db = event.target.result; console.log("数据库连接成功"); resolve(db); }; // 数据库连接失败回调 request.onerror = (event) => { console.error("数据库连接失败"); reject(event.target.error); }; // 数据库更新回调 request.onupgradeneeded = (event) => { db = event.target.result; console.log("数据库更新"); resolve(upgradeCallback(db)); }; }); } /** * 创建仓库 * @param {IDBDatabase} db 数据库实例 * @param {string} tableName 仓库名 * @param {object} options 仓库结构 * @param {array} index 索引字符串数组 * @returns {Promise} 仓库实例 */ function createObjectStore(db, storeName, options, index = []) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!options) reject("请传入仓库结构"); // 创建仓库 const objectStore = db.createObjectStore(storeName, options); // 创建索引 index.forEach((item) => objectStore.createIndex(item, item, { unique: false }) ); // 仓库创建成功回调 objectStore.oncomplete = (event) => { console.log("仓库创建成功"); resolve(objectStore); }; // 仓库创建失败回调 objectStore.onerror = (event) => { console.error("仓库创建失败"); reject(event.target.error); }; }); } /** * 新增数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {object} data 待新增数据 * @returns {Promise} 新增数据结果 */ function insert(db, storeName, data) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!data) reject("请传入数据"); const request = db .transaction(storeName, "readwrite") // 创建事务,读写 .objectStore(storeName) // 获取仓库 .add(data); // 添加数据 request.onsuccess = () => { console.log("数据添加成功"); resolve(); }; request.onerror = (event) => { console.error("数据添加失败"); reject(event.target.error); }; }); } /** * 根据主键获取数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {string} key 主键 * @returns {Promise} 获取数据结果 */ function queryByPrimaryKey(db, storeName, key) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!key) reject("请传入主键"); const request = db .transaction(storeName, "readonly") // 创建事务,只读 .objectStore(storeName) // 获取仓库 .get(key); request.onsuccess = (event) => { console.log("数据获取成功"); resolve(event.target.result); }; request.onerror = (event) => { console.error("数据获取失败"); reject(event.target.error); }; }); } /** * 根据游标获取数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @returns {Promise} 获取数据结果 */ function queryByCursor(db, storeName) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); // 存储结果 const result = []; const request = db .transaction(storeName, "readonly") // 创建事务,只读 .objectStore(storeName) // 获取仓库 .openCursor(); // 获取游标 request.onsuccess = (event) => { console.log("数据获取成功"); let cursor = event.target.result; if (cursor) { result.push(cursor.value); cursor.continue(); } else resolve(result); }; request.onerror = (event) => { console.error("数据获取失败"); reject(event.target.error); }; }); } /** * 根据索引获取数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {{name, value}} indexObject 索引对象 * @returns {Promise} 获取数据结果 */ function queryByIndex(db, storeName, indexObject) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!indexObject) reject("请传入索引对象"); else if (!indexObject.name) reject("请传入索引名称"); else if (!indexObject.value) reject("请传入索引值"); const request = db .transaction(storeName, "readonly") // 创建事务,只读 .objectStore(storeName) // 获取仓库 .index(indexObject.name) // 获取索引 .get(indexObject.value); // 获取数据 request.onsuccess = (event) => { console.log("数据获取成功"); resolve(event.target.result); }; request.onerror = (event) => { console.error("数据获取失败"); reject(event.target.error); }; }); } /** * 根据索引和游标获取数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {{name, value}} indexObject 索引对象 * @returns {Promise} 获取数据结果 */ function queryByIndexAndCursor(db, storeName, indexObject) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!indexObject) reject("请传入索引对象"); else if (!indexObject.name) reject("请传入索引名称"); else if (!indexObject.value) reject("请传入索引值"); // 存储结果 const result = []; const request = db .transaction(storeName, "readonly") // 创建事务,只读 .objectStore(storeName) // 获取仓库 .index(indexObject.name) // 获取索引 .openCursor(IDBKeyRange.only(indexObject.value)); // 获取游标 request.onsuccess = (event) => { console.log("数据获取成功"); let cursor = event.target.result; if (cursor) { result.push(cursor.value); cursor.continue(); } else resolve(result); }; request.onerror = (event) => { console.error("数据获取失败"); reject(event.target.error); }; }); } /** * 根据索引和游标并分页获取数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {{name, value}} indexObject 索引对象 * @param {{number, size}} pageObject 分页对象 * @returns {Promise} 获取数据结果 */ function queryByIndexAndCursorAndPagination( db, storeName, indexObject, pageObject ) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!indexObject) reject("请传入索引对象"); else if (!indexObject.name) reject("请传入索引名称"); else if (!indexObject.value) reject("请传入索引值"); if (!pageObject) reject("请传入分页对象"); else if (!pageObject.number) reject("请传入页码"); else if (pageObject.number < 1) reject("页码不能小于 1"); else if (!pageObject.size) reject("请传入页大小"); else if (pageObject.size < 1) reject("页大小不能小于 1"); // 存储结果 const result = []; let counter = 0, // 计数器 advanced = true; // 是否已跳过指定页码 const request = db .transaction(storeName, "readonly") // 创建事务,只读 .objectStore(storeName) // 获取仓库 .index(indexObject.name) // 获取索引 .openCursor(IDBKeyRange.only(indexObject.value)); // 获取游标 request.onsuccess = (event) => { console.log("数据获取成功"); let cursor = event.target.result; // 跳过指定页码 if (pageObject.number > 1 && advanced) { advanced = false; cursor.advance((pageObject.number - 1) * pageObject.size); return; } if (cursor) { result.push(cursor.value); counter++; if (counter < pageObject.size) cursor.continue(); // 继续获取下一条数据 else resolve(result); } else resolve(result); }; request.onerror = (event) => { console.error("数据获取失败"); reject(event.target.error); }; }); } /** * 修改数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {object} data 待修改数据 * @returns {Promise} 修改数据结果 */ function update(db, storeName, data) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!data) reject("请传入数据"); else if (!data.id) reject("请传入数据 ID"); const request = db .transaction(storeName, "readwrite") // 创建事务,读写 .objectStore(storeName) // 获取仓库 .put(data); // 修改数据 request.onsuccess = () => { console.log("数据修改成功"); resolve(); }; request.onerror = (event) => { console.error("数据修改失败"); reject(event.target.error); }; }); } /** * 根据主键删除数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {string} key 主键 * @returns {Promise} 获取数据结果 */ function deleteByPrimaryKey(db, storeName, key) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!key) reject("请传入主键"); const request = db .transaction(storeName, "readwrite") // 创建事务,读写 .objectStore(storeName) // 获取仓库 .delete(key); // 删除数据 request.onsuccess = (event) => { console.log("数据删除成功"); resolve(event.target.result); }; request.onerror = (event) => { console.error("数据删除失败"); reject(event.target.error); }; }); } /** * 根据索引和游标删除数据 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @param {{name, value}} indexObject 索引对象 * @returns {Promise} 删除数据结果 */ function deleteByIndexAndCursor(db, storeName, indexObject) { return new Promise((resolve, reject) => { if (!db) reject("请先连接数据库"); if (!storeName) reject("请传入仓库名称"); if (!indexObject) reject("请传入索引对象"); else if (!indexObject.name) reject("请传入索引名称"); else if (!indexObject.value) reject("请传入索引值"); const request = db .transaction(storeName, "readwrite") // 创建事务,读写 .objectStore(storeName) // 获取仓库 .index(indexObject.name) // 获取索引 .openCursor(IDBKeyRange.only(indexObject.value)); // 获取游标 request.onsuccess = (event) => { let cursor = event.target.result; if (cursor) { const deleteRequest = cursor.delete(); deleteRequest.onsuccess = () => { console.log("数据删除成功"); }; deleteRequest.onerror = () => { console.error("数据删除失败"); reject(deleteRequest.error); }; cursor.continue(); } else resolve(); }; request.onerror = (event) => { console.error("数据删除失败"); reject(event.target.error); }; }); } /** * 删除仓库 * @param {IDBDatabase} db 数据库对象 * @param {string} storeName 仓库名称 * @returns {Promise} 删除仓库结果 */ function deleteObjectStore(db, storeName) { return new Promise((resolve, reject) => { if (!storeName) reject("仓库名称不能为空"); if (db.objectStoreNames.contains(storeName)) { const deleteRequest = db.deleteObjectStore(storeName); deleteRequest.onsuccess = () => { console.log("仓库删除成功"); resolve(); }; deleteRequest.onerror = (event) => { console.error("仓库删除失败"); reject(event.target.error); }; } else { reject("仓库不存在"); } }); } /** * 删除数据库 * @param {string} dbName 数据库名称 * @returns {Promise} 删除数据库结果 */ function deleteDatabase(dbName) { return new Promise((resolve, reject) => { if (!dbName) reject("请输入数据库名称"); if (!currentIndexedDB) reject("请先初始化数据库"); const request = currentIndexedDB.deleteDatabase(dbName); request.onsuccess = () => { console.log("数据库删除成功"); resolve(); }; request.onerror = (event) => { console.error("数据库删除失败"); reject(event.target.error); }; }); } /** * 关闭数据库连接 * @param {IDBDatabase} db 数据库对象 */ function disconnect(db) { if (!db) return; db.close(); console.log("数据库已断开连接"); } -
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>IndexedDB</title> <script src="./script.js"></script> </head> <body> <button onclick="init()">初始化数据库</button> <fieldset> <legend>新增数据</legend> <form> <label>姓名:<input type="text" name="name" /></label> <label>邮箱:<input type="text" name="email" /></label> <input type="button" value="保存" onclick="insertSubmit(event)" /> </form> </fieldset> <fieldset> <legend>查询数据</legend> <fieldset> <legend>根据主键查询</legend> <form> <label>主键:<input type="text" name="pk" /></label> <input type="button" value="查询" onclick="querySubmit(event, 'pk')" /> </form> </fieldset> <fieldset> <legend>根据游标查询</legend> <form> <input type="button" value="查询所有" onclick="querySubmit(event, 'cursor')" /> </form> </fieldset> <fieldset> <legend>根据索引查询</legend> <form> <label>姓名:<input type="text" name="name" /></label> <input type="button" value="查询" onclick="querySubmit(event, 'index')" /> </form> </fieldset> <fieldset> <legend>根据索引和游标查询</legend> <form> <label>姓名:<input type="text" name="name" /></label> <input type="button" value="查询" onclick="querySubmit(event, 'index-cursor')" /> </form> </fieldset> <fieldset> <legend>根据索引和游标并分页查询</legend> <form> <label> 邮箱: <input type="text" name="email" /> </label> <br /> <label> 页码: <input type="number" min="1" name="page-number" /> </label> <label> 每页数量:<input type="number" min="1" name="page-size" /> </label> <input type="button" value="查询" onclick="querySubmit(event, 'index-cursor-pagination')" /> </form> </fieldset> <fieldset> <legend>查询结果</legend> <ul id="result"></ul> </fieldset> </fieldset> <fieldset> <legend>修改数据</legend> <form> <label>主键:<input type="text" name="pk" /></label> <label>姓名:<input type="text" name="name" /></label> <label>邮箱:<input type="text" name="email" /></label> <input type="button" value="保存" onclick="updateSubmit(event)" /> </form> </fieldset> <fieldset> <legend>删除数据</legend> <fieldset> <legend>根据主键删除</legend> <form> <label>主键:<input type="text" name="pk" /></label> <input type="button" value="删除" onclick="deleteSubmit(event, 'pk')" /> </form> </fieldset> <fieldset> <legend>根据索引和游标删除</legend> <form> <label>姓名:<input type="text" name="name" /></label> <input type="button" value="删除" onclick="deleteSubmit(event, 'index-cursor')" /> </form> </fieldset> </fieldset> <button onclick="deleteObjectStoreSubmit()">删除仓库</button> <button onclick="deleteDatabaseSubmit()">删除数据库</button> <button onclick="disconnectSubmit()">断开连接</button> <script> const DATABASE_NAME = "myDB", USERS_STORE = "users"; let db = null; // 生成 uuid function uuidv4() { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { var r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8; return v.toString(16); }); } // 初始化数据库 async function init() { db = await connect(DATABASE_NAME, 1, async (database) => { await createObjectStore(database, USERS_STORE, { keyPath: "id" }, [ "name", "email", ]); }); } // 保存表单数据到数据库 async function insertSubmit(event) { var form = event.target.form; var id = await insert(db, USERS_STORE, { id: uuidv4(), name: form.name.value, email: form.email.value, }); } // 查询表单数据 async function querySubmit(event, type) { var form = event.target.form; var result = document.getElementById("result"); result.innerHTML = ""; var data = []; switch (type) { case "pk": data = [await queryByPrimaryKey(db, USERS_STORE, form["pk"].value)]; break; case "cursor": data = await queryByCursor(db, USERS_STORE); break; case "index": data = [ await queryByIndex(db, USERS_STORE, { name: "name", value: form["name"].value, }), ]; break; case "index-cursor": data = await queryByIndexAndCursor(db, USERS_STORE, { name: "name", value: form["name"].value, }); break; case "index-cursor-pagination": data = await queryByIndexAndCursorAndPagination( db, USERS_STORE, { name: "email", value: form["email"].value, }, { number: parseInt(form["page-number"].value), size: parseInt(form["page-size"].value), } ); break; default: alert("未知查询类型"); break; } // 创建结果列表 const fragment = document.createDocumentFragment(); if (data.length > 0) data.forEach((item) => { const li = document.createElement("li"); li.textContent = item.name + " " + item.email; fragment.appendChild(li); }); result.appendChild(fragment); } // 修改表单数据 async function updateSubmit(event) { var form = event.target.form; await update(db, USERS_STORE, { id: form.pk.value, name: form.name.value, email: form.email.value, }); } // 删除表单数据 async function deleteSubmit(event, type) { var form = event.target.form; switch (type) { case "pk": await deleteByPrimaryKey(db, USERS_STORE, form["pk"].value); break; case "index-cursor": await deleteByIndexAndCursor(db, USERS_STORE, { name: "name", value: form["name"].value, }); break; default: alert("未知删除类型"); break; } } // 删除仓库 async function deleteObjectStoreSubmit() { db = await connect(DATABASE_NAME, 2, async (database) => { await deleteObjectStore(database, USERS_STORE); }); } // 删除数据库 async function deleteDatabaseSubmit() { try { await deleteDatabase(DATABASE_NAME); db = null; } catch (error) {} } // 断开连接 function disconnectSubmit() { disconnect(); db = null; } </script> </body> </html>
-End-

浙公网安备 33010602011771号