前言
IndexedDB是一个事务数据库系统,类似于基于SQL的RDBMS。然而,与使用固定列表的基于SQL的RDBMS不同,IndexedDB是一个基于JavaScript的面向对象数据库。
为啥要使用呢?
随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据储存在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。
现有的浏览器数据储存方案,都不适合储存大量数据:Cookie 的大小不超过4KB,且每次请求都会发送回服务器;localStorage 和 sessionStorage 在 2.5MB 到 10MB 之间(各家浏览器不同),而且不提供搜索功能,不能建立自定义的索引。所以,需要一种新的解决方案,这就是 IndexedDB 诞生的背景。
一、IndexedDB是什么?
通俗地说,IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
IndexedDB 具有以下特点。
-
键值对储存。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
-
异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
-
支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
-
同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
-
储存空间大 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
-
支持二进制储存。 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
更多详细的介绍可移步这里查看
二、使用步骤
1、封装常用方法(创建构造函数)
模块化开发是前端必备技巧,下面咱简单封装个IndexDBCache
类,用于增、删、改、查 操作
const indexDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
export class IndexDBCache {
constructor(
params = {
dbName: "test", // 数据库名
cacheTableName: "imageCache", // 表名
keyPath: "imageName", // 设置主键 (需要为添加对象内的key,否则新增和获取都会失败)
indexs: [], // 设置索引
}
) {
this._db = null; // 数据库
this._transaction = null; // 事务
this._request = null; // 打开数据库返回的事务
this._dbName = params.dbName; // 数据库名
this._cacheTableName = params.cacheTableName; // 表名
this._dbversion = 1; // 数据库版本
this._keyPath = params.keyPath; // 设置主键
this._indexs = params.indexs; // 设置索引
}
// 初始化数据库
initDB() {
return new Promise((resolve, reject) => {
// 打开数据库
this._request = indexDB.open(this._dbName, this._dbversion);
// 数据库初始化成功
this._request.onsuccess = (event) => {
this._db = this._request.result;
resolve(event);
};
// 数据库初始化失败
this._request.onerror = (event) => {
reject(event);
};
// 数据库初次创建或更新时(指定的版本号,大于数据库的实际版本号)会触发
this._request.onupgradeneeded = (event) => {
// 这时通过事件对象的target.result属性,拿到数据库实例。
const db = event.target.result;
if (!db.objectStoreNames.contains(this._cacheTableName)) {
const objectStore = db.createObjectStore(this._cacheTableName, {
keyPath: this._keyPath,
});
this._indexs.forEach(element => {
// 三个参数分别为索引名称、索引所在的属性、配置对象(说明该属性是否包含重复的值)
objectStore.createIndex(element.name, element.name, { unique: element.unique });
});
}
resolve(event);
};
});
}
/**
* @description : 新增数据
* @param {Object} params 添加到数据库中的数据 { imageName: 文件名, image: base64格式图片 }
* @return {*}
*/
addData(params) {
return new Promise((resolve, reject) => {
const transaction = this._db.transaction(
this._cacheTableName,
"readwrite"
);
const store = transaction.objectStore(this._cacheTableName);
const response = store.add(params);
response.onsuccess = (event) => {
resolve(event);
};
response.onerror = (event) => {
reject(event);
};
});
}
// 删除指定主键值
remove(key) {
var request = this._db.transaction([this._cacheTableName], 'readwrite')
.objectStore(this._cacheTableName)
.delete(key);
request.onsuccess = function (event) {
console.log('数据删除成功');
};
}
// 清空数据库数据
clearDB() {
return new Promise((resolve, reject) => {
const transaction = this._db.transaction(
this._cacheTableName,
"readwrite"
);
const store = transaction.objectStore(this._cacheTableName);
const response = store.clear();
response.onsuccss = (event) => {
resolve(event);
};
response.onerror = (event) => {
reject(event);
};
});
}
// 通过主键读取数据
getDataByKey(key) {
return new Promise((resolve, reject) => {
const transaction = this._db.transaction(this._cacheTableName);
const objectStore = transaction.objectStore(this._cacheTableName);
// 通过主键读取数据
// const request = objectStore.get(key);
// getAll(key)同get(key)获取指定key对应数据,如果getAll不传参或者传null即返回所有数据
const request = objectStore.getAll(key);
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = (event) => {
reject(event);
};
});
}
// 通过主键读取数据
getDataByIndex(params) {
const transaction = this._db.transaction([this._cacheTableName], 'readonly');
const store = transaction.objectStore(this._cacheTableName);
const index = store.index(params.index);
const request = index.get(params.value);
request.onsuccess = function (e) {
const result = e.target.result;
console.log('getDataByIndex', result)
}
}
// 遍历数据
readAll() {
const objectStore = this._db.transaction(this._cacheTableName).objectStore(this._cacheTableName);
objectStore.openCursor().onsuccess = function (event) {
const cursor = event.target.result;
if (cursor) {
console.log('key: ' + cursor.key);
console.log('Value: ' + JSON.stringify(cursor.value));
cursor.continue();
} else {
console.log('没有更多数据了!');
}
};
}
// 更新指定主键数据
update(params) {
var request = this._db.transaction([this._cacheTableName], 'readwrite')
.objectStore(this._cacheTableName)
.put(params);
request.onsuccess = function (event) {
console.log('数据更新成功');
};
request.onerror = function (event) {
console.log('数据更新失败');
}
}
// 关闭数据库
closeDB() {
this._db.close();
}
// 删除数据库
deleteDB() {
console.log('开始删除数据库')
let DBDeleteRequest = indexDB.deleteDatabase(this._dbName)
DBDeleteRequest.onsuccess = function (event) {
console.log('删除数据库成功')
}
DBDeleteRequest.onerror = function (event) {
console.log('删除数据库失败')
}
}
}
2、实例化并执行各种操作
初始化数据库
// 根据项目实际需求,设置对应数据库名、表名和数据库主键(主键需要为添加对象内的key,否则新增和获取会失败)
const params = {
dbName: "test",
cacheTableName: "imageCache",
keyPath: "imageName",
indexs: [
{name: 'imageData', unique: false},
{name: 'imageFile', unique: true}
]
}
let imageDB = new IndexDBCache(params)
const initIndexDB = () => {
imageDB.initDB().then(res => {
if (res.type == 'upgradeneeded') {
console.log('indexDB 数据库创建或更新成功!')
} else {
console.log('indexDB 数据库初始化成功!')
}
}).catch((err) => {
console.log('indexDB 数据库初始化失败! ', err)
})
}
添加数据
function RandomNumber() {
return Math.floor(Math.random() * 100000000.0)
}
const changeVal = () => {
const data = { imageName: 'uploadImgName' + RandomNumber(), imageData: 'uploadImgUrl' + RandomNumber(), imageFile: 'uploadFile' + RandomNumber() }
imageDB.addData(data).then((res) => {
console.log('写入 indexDB 数据库成功', res)
}).catch((err) => {
console.log('写入 indexDB 数据库失败==>', err)
})
}
添加的时候需要注意添加对象内的key是否有存在于keyPath中,如果都没有,那么新增数据会导致失败,如下图
删除数据
- 删除指定主键值
const removeIndexDB = () => {
imageDB.remove('uploadImgName42424198')
}
原始数据
删除后
- 清空数据库的数据
const clearIndexDB = () => {
imageDB.clearDB()
}
更新数据
当前例子以imageName
为主键
const updateIndexDB = () => {
imageDB.update({
imageData: "uploadImgUrl",
imageFile: "uploadFile",
imageName: "uploadImgName33027705"
})
}
查询数据
- 通过主键获取数据
// 从数据库获取数据
// imageName 可为空或者 null,即返回所有数据否则是指定key对应的值
const getImageByName = (imageName) => {
imageDB.getDataByKey(imageName).then((res) => {
console.log('从indexDB数据库获取数据成功', res)
}).catch((err) => {
console.log('从indexDB数据库获取数据失败==>', err)
})
}
- 通过索引获取数据
const getDataByIndexDB = () => {
imageDB.getDataByIndex({
index: 'imageData',
value: 'uploadImgUrl57913099'
})
}
关闭数据库
const closeIndexDB = () => {
imageDB.closeDB()
}
如下图,关闭数据库之后,再次获取数据会提示数据库已关闭,如果关闭后需要重新获取数据,则需执行imageDB.initDB()
打开数据库