html
复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>IndexedDB 简单示例</title>
<style>
body { font-family: sans-serif; max-width: 600px; margin: 40px auto; }
input, button { margin: 4px; padding: 6px 12px; }
#output { white-space: pre-wrap; background: #f5f5f5; padding: 12px; margin-top: 16px; border-radius: 4px; }
</style>
</head>
<body>
<h2>IndexedDB 简单示例</h2>
<div>
<input id="keyInput" placeholder="键 (key)" />
<input id="valueInput" placeholder="值 (value)" />
</div>
<div style="margin-top: 8px;">
<button onclick="setItem()">存数据</button>
<button onclick="getItem()">取数据</button>
<button onclick="removeItem()">删数据</button>
<button onclick="getAllData()">查询全部</button>
</div>
<div id="output">结果将显示在这里...</div>
<script>
/**
* 根据数据库名称,存放打开数据库的请求,一个数据库始终保持只有一个连接
* - 方便获取数据库的连接
* - 关闭指定数据库的连接
*/
const dbRequestsDic = {}
/**
* 根据数据库名和表名获取数据库连接,没有则创建
* - 调用 createObjectStore 来创建表,
* 创建表必须在数据库请求的 onupgradeneeded 回调中执行,
* onupgradeneeded 回调的触发条件是数据库创建或升级,
* 而数据库升级前还必须关闭该数据库的所有连接
* @param DBName 数据库名称
* @param tableName 表名
* @returns Promise<IDBDatabase>
*/
function getDatabase(DBName, tableName) {
return new Promise((resolve, reject) => {
// 复用已有的打开请求,保证同一个数据库只有一个连接
dbRequestsDic[DBName] = dbRequestsDic[DBName] || indexedDB.open(DBName)
const request = dbRequestsDic[DBName]
if (request.readyState === 'done') {
// 请求已完成,可以直接拿到数据库连接
const dataBase = request.result // 表示一个数据库连接
if (dataBase.objectStoreNames.contains(tableName)) {
// 表已存在,直接返回数据库连接
resolve(request.result)
} else {
// 表不存在,需要关闭连接 → 升级版本 → 重新打开 → 建表
const newVersion = dataBase.version + 1 // 版本号 +1,确保触发升级
dataBase.close() // 升级前必须关闭当前连接
const newRequest = indexedDB.open(DBName, newVersion) // 用新版本号重新打开
// upgradeneeded:数据库版本变更时触发,这是唯一能建表的时机
newRequest.addEventListener('upgradeneeded', () => {
newRequest.result.createObjectStore(tableName, { keyPath: 'id' }) // 新增表,主键为 id
})
dbRequestsDic[DBName] = newRequest // 更新字典,保存新请求
newRequest.addEventListener('success', () => resolve(newRequest.result))
newRequest.addEventListener('error', reject)
}
} else if (request.readyState === 'pending') {
// 请求还在进行中(比如第一次调用,数据库正在创建/打开)
// upgradeneeded:数据库首次创建或版本升级时触发
request.addEventListener('upgradeneeded', () => {
if (!request.result.objectStoreNames.contains(tableName)) {
request.result.createObjectStore(tableName, { keyPath: 'id' }) // 表不存在则建表
}
})
request.addEventListener('success', () => resolve(request.result))
request.addEventListener('error', reject)
}
})
}
class DBCache {
/**
* IndexedDB 构造函数
* @param DBName 数据库名称
* @param storageName 仓库名称(表名)
*/
constructor(DBName, storageName) {
this.DBName = DBName
this.storageName = storageName
}
/** 数据库名称 */
DBName
/** 仓库名称 */
storageName
/** 私有方法:获取数据库连接 */
#getDB() {
return getDatabase(this.DBName, this.storageName)
}
/**
* 私有方法:内部查询,只返回 value 部分
* 存储结构是 { id, value },对外只返回 value,id 对使用者透明
*/
#_getItem(key) {
return new Promise(async (resolve, reject) => {
const db = await this.#getDB()
const transaction = db.transaction([this.storageName]) // 只读事务
const objectStore = transaction.objectStore(this.storageName) // 获取仓库对象
const request = objectStore.get(key) // 按主键查询
request.onerror = reject
request.onsuccess = function () {
// request.result 是 { id: key, value: data },只返回 value 部分
resolve(request.result == undefined ? undefined : request.result.value)
}
})
}
/**
* 存储数据
* @param key 键
* @param data 需要存储的数据,不能存储 Proxy 对象(如 Vue 3 的 reactive)
* 先查询 key 是否存在:存在用 put 更新,不存在用 add 新增
*/
setItem(key, data) {
return new Promise(async (resolve, reject) => {
const db = await this.#getDB()
const oldData = await this.#_getItem(key) // 先查是否已存在
const transaction = db.transaction([this.storageName], 'readwrite') // 读写事务
const objectStore = transaction.objectStore(this.storageName)
let request
if (oldData) {
request = objectStore.put({ id: key, value: data }) // 已存在 → put 更新
} else {
request = objectStore.add({ id: key, value: data }) // 不存在 → add 新增
}
request.onsuccess = resolve
request.onerror = reject
})
}
/**
* 根据id获取数据
* @param key 键
* @returns value 部分,不存在则返回 undefined
*/
getItem(key) {
return this.#_getItem(key)
}
/**
* 根据id删除数据
* @param key 键
*/
removeItem(key) {
return new Promise(async (resolve, reject) => {
const db = await this.#getDB()
const transaction = db.transaction([this.storageName], 'readwrite') // 读写事务
const objectStore = transaction.objectStore(this.storageName)
const request = objectStore.delete(key) // 按主键删除
request.onerror = reject
request.onsuccess = resolve
})
}
}
// 创建实例
const cache = new DBCache('myDatabase', 'myStore')
// ─── 页面交互 ───
async function setItem() {
const key = document.getElementById('keyInput').value.trim()
const value = document.getElementById('valueInput').value.trim()
if (!key || !value) return show('请输入键和值')
try {
await cache.setItem(key, value)
show(`存储成功: { id: "${key}", value: "${value}" }`)
} catch (e) {
show(`存储失败: ${e}`)
}
}
async function getItem() {
const key = document.getElementById('keyInput').value.trim()
if (!key) return show('请输入键')
try {
const result = await cache.getItem(key)
show(result !== undefined ? `查询结果: ${JSON.stringify(result)}` : `未找到 key="${key}"`)
} catch (e) {
show(`查询失败: ${e}`)
}
}
async function removeItem() {
const key = document.getElementById('keyInput').value.trim()
if (!key) return show('请输入键')
try {
await cache.removeItem(key)
show(`删除成功: key="${key}"`)
} catch (e) {
show(`删除失败: ${e}`)
}
}
async function getAllData() {
try {
const db = await getDatabase('myDatabase', 'myStore')
const request = db.transaction(['myStore'])
.objectStore('myStore')
.getAll()
request.onsuccess = (e) => {
const results = e.target.result
show(results.length ? `全部数据:\n${JSON.stringify(results, null, 2)}` : '仓库为空')
}
request.onerror = (e) => show(`查询失败: ${e.target.error}`)
} catch (e) {
show(`查询失败: ${e}`)
}
}
function show(msg) {
document.getElementById('output').textContent = msg
}
</script>
</body>
</html>