目录
前言:
在浏览器上有两种数据库: webSQL和IndexedDB。但是如果在浏览器上需要用到数据库一般会使用Indexed DB数据库,webSQL基本上已经废弃了。
WebSQL 是一种基于 SQL 的浏览器本地存储技术,在 HTML5 中是一个非标准化技术。虽然 WebSQL 在一些场景下使用起来比较方便,但是由于其不被 W3C 标准化,导致了一些问题:
- 浏览器之间的兼容性问题。不同浏览器对 WebSQL 的支持程度不一样,甚至有些浏览器完全不支持,这导致了 WebSQL 在移动端和 PC 端之间的兼容性问题。
- 安全性问题。WebSQL 存在一些潜在的安全风险,例如攻击者可以通过注入恶意 SQL 语句来获取用户数据等。
- 数据库规模限制。WebSQL 的数据库容量限制是各个浏览器自己实现的,没有一个通用的标准,而且大部分浏览器都对数据库大小进行了限制。
因此,W3C 推出了 IndexedDB 标准,作为 WebSQL 的替代方案,并得到了主流浏览器的支持。IndexedDB 具有更好的跨平台兼容性,安全可靠,支持事务和异步操作等特性,能够更好地应对现代移动端应用和在线数据存储应用的需求。
(前端存储方案有如cookie、sessionstorage等等)
1.IndexedDB 简介
MDN官网是这样解释 Indexed DB的:
IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据 (也包括文件/二进制大型对象 (blobs))。该 API 使用索引实现对数据的高性能搜索。虽然 Web Storage 在存储较少量的数据很有用,但对于存储更大量的结构化数据来说力不从心。而 IndexedDB 提供了这种场景的解决方案。
cookie、localStorage 等存储方式都有存储大小限制,如果数据量很大,且都需要客户端存储时,则使用 IndexedDB 数据库。
会话期 Cookie | 持久性 Cookie | sessionSto rage |
localStora ge |
indexedDB | WebsQL | |
存储大小 | 4kb | 4kb | 2.5~10MB | 2.5~10MB | >250MB | 已废弃 |
失效时间 | 浏览器关闭自动清除 | 设置过期时间,到 期后清除 |
浏览器关 闭后清除 |
永久保存 (除非手 动清除) |
手动更新 或删除 |
已废弃 |
与服务端交互 | 有 | 有 | 无 | 无 | 无 | 已废弃 |
访问策略 | 符合同源策略可以访问 | 符合同源策略可以 访问 |
符合同源 策略可以 访问 |
即使同源 也不可相 互访问 |
符合同源 策略可以 已废弃 访问器 |
已废弃 |
2.IndexedDB 使用场景
所有的场景都基于客户端需要存储大量数据的前提下:
- 数据可视化等界面,大量数据,每次请求会消耗很大性能。
- 即时聊天工具,大量消息需要存在本地。
- 其它存储方式容量不满足时,不得已使用 IndexedDB
3.IndexedDB 特点
(1) 非关系型数据库(NoSql)
MySQL等数据库都是关系型数据库,它们的主要特点就是数据都以一张二维表的形式存储,而Indexed DB是非关系型数据库,主要以键值对的形式存储数据。
(2) 持化存储
cookie、localStorage、 sessionStorage 等方式存储的数据当我们清楚浏览器缓存后,这些数据都会被清除掉的,而使用IndexedDB存储的数据则不会,除非手动删除该数据库。
(3)异步操作
IndexedDB 操作时不会锁死浏览器(异步),用户依然可以进行其他的操作,这与localstorage形成鲜明的
对比,后者是同步的。
(4)支持事务
IndexedDB 支持事务 (transaction),这意味着一系列的操作步骤之中,只要有一步失败了,整个事务都会取消,数据库回滚的事务发生之前的状态,这和MySQL等数据库的事务类似。
(5) 同源策略
IndexedDB同样存在同源限制,每个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
(6)存储容量大
IndexedDB最显著的特点之一了,也是不用localStorage等存储方式的最好理由。
4.IndexedDB 概念补充
①仓库objectStore
IndexedDB 它只有仓库 store 的概念,可以把仓库理解为表即可,即一个 store 没有表的概念,是一张表。
②索引index
在关系型数据库当中也有索引的概念,我们可以给对应的表字段添加索引,以便加快查找速率。在IndexedDB 中同样有索引,可以在创建store的时候同时创建索引,在后续对store进行查询的时候即可通过索引来筛选,给某个字段添加索引后,在后续插入数据的过成功,索引字段便不能为空。
③游标cursor
游标是IndexedDB数据库新的概念,可以把游标想象为一个指针,当要查询满足某一条件的所有数据时,就需要用到游标,让游标一行一行的往下走,游标走到的地方便会返回这一行数据,此时便可对此行数据进行判断,是否满足条件。
⭕注意:IndexedDB 查询不像 MySQL等数据库方便它只能通过主键、索引、游标方式查询数据。
④事务
IndexedDB支持事务,即对数据库进行操作时,只要失败了的一致性。都会回滚到最初始的状态,确保数据
5.IndexedDB 实操
IndexedDB 所有针对仓库的操作都是基于事务的。
①创建或连接数据库
//打开数据库
//@param {object} dbName 数据库的名字
//@param {string} storeName 仓库名称
//@param {string} version 数据库的版本
//@return {object} 该函数会返回一个数据库实例
function openDB(dbName, version = 1) {
return new Promise((resolve, reject) =>{
// 兼容浏览器
var indexedDB =
window.indexedDB ||
window.mozIndexedDB ||
window.webkitIndexedDB ||
window.msIndexedDB;
let db;
// 打开数据库,若没有则会创建
const request = indexedDB.open(dbName,version);
//数据库打开成功回调
request.onsuccess = fuction(evetn){
db = event.target.result; //数据库对象
console.log("数据库打开成功");
resolve(db);
};
//数据库打开失败的回调
request .onerror= function(event){
console.log("数据库打开报错");
};
//数据库有更新时候的回调
request.onupgradeneeded = function (event){
// 数据库创建或升级的时候会触发
console.log("onupgradeneeded");
db = event.target.result; //数据库对象var objectstore;
// 创建存储库
objectstore = db.createobjectstore("signalChat",{
keyPath:"sequenceId",// 这是主键
// autoIncrement: true // 实现自增
});
// 创建索引,在后面查询数据的时候可以根据索引查
objectStore.createIndex("link","link", { unique:false});
objectstore.createIndex("sequenceId","sequenceId", {unique: false});
objectStore.createIndex("messageType","messageType",{
unique: false,
});
};
});
}
将创建数据库的操作封装成了一个函数,并且该函数返回一个 promise 对象,使得在调用的时候可以链式调用,函数主要接收两个参数: 数据库名称、数据库版本。函数内部主要有三个回调函数,分别是:
- onsuccess: 数据库打开成功或者创建成功后的回调,这里我们将数据库实例返回了出去。
- onerror: 数据库打开或创建失败后的回调。
- onupgradeneeded: 当数据库版本有变化的时候会执行该函数,比如我们想创建新的存储库(表),就可以在该函数里面操作,更新数据库版本即可。
② 插入数据
function addData(db, storeName, data) {
var request = db
.transaction([storeName],"readwrite") // 事务对象 指定表格名称和操作模式 ("只读"或"读写")
.objectStore(storeName) // 仓库对象
.add(data);
request.onsuccess = function (event){
console.log("数据写入成功");
};
request.onerror = function (event) {
console.log("数据写入失败");
};
}
IndexedDB 插入数据需要通过事务来进行操作,插入的方法也很简单,利用 IndexedDB 提供的 add方法即可,这里同样将插入数据的操作封装成了一个函数,接收三个参数,分别如下:
- db: 在创建或连接数据库时,返回的db实例,需要那个时候保存下来
- storeName: 仓库名称(或者表名),在创建或连接数据库时就已经创建好了仓库。
- data: 需要插入的数据,通常是一个对象
⭕注意 : 插入的数据是一个对象,而且必须包含声明的索引键值对
③通过主键读取数据
function getDataByKey(db, storeName, key) {
return new Promise((resolve, reject) => {
var transaction = db.transaction([storeName]); // 事务
var objectstore = transaction.objectstore(storeName); // 仓库对象
var request = objectstore.get(key); // 通过主键获取数据
request.onerror = function (event) {
console.log("事务失败");
};
request.onsuccess = function (event) {
console.log("主键查询结果:",,request.result);
resolve(request.result);
};
});
}
④通过游标查询数据
function cursorGetData(db, storeName){
let list = [];
var store = db
.transaction(storeName,"readwrite") // 事务
.objectstore(storeName); // 仓库对象
var request = store.openCursor(); // 指针对象
// 游标开启成功,逐行读数据
request.onsuccess = function (e) {
var cursor = e.target.result;
if (cursor) {
// 必须要检查
list.push(cursor.value);
cursor.continue(); // 遍历了存储对象中的所有内容
} else {
console.log("游标读取的数据",list);
}
};
}
⑤通过索引查询数据
function getDataByIndex(db,storeName, indexName, indexValue){
var store = db,transaction(storeName, "readwrite").objectstore(storeName);
var request = store.index(indexName).get(indexValue);
request.onerror = function () {
console.log("事务失败");
};
request.onsuccess = function (e) {
var result = e.target.result;
console.log("索引查询结果:",result);
};
}
索引名称 即创建仓库的时候创建的索引名称,也就是键值对中的键,最终会查询出所有满足传入函数索引值的数据。
索引名称即创建仓库的时候创建的索引名称,也就是键值对中的键。最终会查询出仅一条传入函数索引值的数据。
⑥通过索引和游标查询数据
单独通过索引或者游标查询出的数据都是部分或者所有数据,如果想要查询出索引中满足某些条件的所有数据,那么单独使用索引或游标是无法实现的。当然,可以查询出所有数据之后在循环数组筛选出合适的数据,但是这不是最好的实现方式,最好的方式当然是将索引和游标结合起来。
function cursorGetDataByIndex(db,storeName ,indexName ,indexValue ){
let list = [];
var store = db.transaction(storeName."readwrite").objectstore(storeName); // 仓库对象
var request = store
.index(indexName) // 索引对象
.openCursor(IDBKeyRange.only(indexValue)); // 指针对象
request.onsuccess = function (e) {var cursor = e.target.result;
if (cursor) {
// 必须要检查
list.push(cursor.value);
cursor.continue(); // 遍历了存储对象中的所有内容
} else {
console.log("游标索引查询结果:",list);
}
};
request.onerror = function (e) {};
}
⑦通过索引和游标分页查询
IndexedDB 分页查询 不像 MySQL 分页查询那么简单,没有提供现成的API,如 limit 等,所以需要自己实现分页。
function cursorGetDataByIndexAndPage(
db , //数据库实例
storeName,
indexName,
indexValue,
page,
pageSize
){
let list = []; //设置空数组用来保存实例
let counter = ; // 计数器——停止游标
let advanced = true; // 是否跳过多少条查询
var store = db.transaction(storeName, "readwrite"),obiectStore(storeName): // 库对象
var request = store
.index(indexName) // 索引对象
.openCursor(IDBKeyRange.only(indexValue)); // 指针对象
request.onsuccess = function (e) {
var cursor = e.target.result;
if (page > 1 && advanced) {
advanced = false; //是否跳转
cursor.advance((page - 1) * pagesize); // 跳过多少条
return;
}
if (cursor) {
// 必须要检查
list.push(cursor.value);
counter++;
if (counter < pageSize) [
cursor.continue(); // 遍历了存储对象中的所有内容
} else {
cursor = null;
console.log("分页查询结果",,list);
}
} else {
console.log("分页查询结果",list);
}
};
request.onerror = function (e) {};
}
IndexedDB的一个APl: advance。该函数可以让游标跳过多少条开始查询。假如额分页是每页10条数据,现在需要查询第2页,那么就需要跳过前面10条数据,从11条数据开始查询,直到计数器等于10,那么就关闭游标,结束查询。
⑧更新数据
IndexedDB更新数据较为简单,直接使用 put 方法,值得注意的是如果数据库中没有该条数据,则会默认增加该条数据,否则更新。喜欢更新和新增都是用put方法,这也是可行的。
function updateDB(db, storeName, data) {
var request = db
.transaction([storeName],"readwrite") // 事务对象
.objectstore(storeName) // 仓库对象
.put(data);
request.onsuccess = function () {
console.log("数据更新成功");
};
request.onerror = function () {
console.log("数据更新失败");
};
}
put方法接收一个数据对象
⑨通过主键删除数据
主键即创建数据库时申明的 keyPath,它是唯一的。
function deleteDB(db, storeName, id) {
var request = db
.transaction([storeName],"readwrite")
.objectstore(storeName)
.delete(id);
request.onsuccess = function () {
console.log("数据删除成功");
};
request.onerror = function () {
console.log("数据删除失败");
};
}
⑩通过索引和游标删除指定数据
有时候拿不到主键值,只能只能通过索引值来删除,通过这种方式,可以删除一条数据(索引值唯一)或者所有满足条件的数据 (索引值不唯一)。
function cursorDelete(db,storeName,indexName,indexValue){
var store = db,transaction(storeName,"readwrite").objectstore(storeName);
var request = store
.index(indexName) // 索引对象
.openCursor(IDBKeyRange.only(indexValue)); // 指针对象
request.onsuccess = function (e) {
var cursor = e.target.result;
var deleteRequest;
if (cursor) {
deleteRequest = cursor.delete(); // 请求删除当前项
deleteRequest.onerror = function () {
console.log("游标删除该记录失败");
};
deleteRequest.onsuccess = function () {
console.log("游标删除该记录成功");
};
cursor.continue();
}
};
request.onerror = function (e){ };
}
上段代码可以删除索引值为indexValue的所有数据,值得注意的是使用 IDBKeyRange.only0API,该API代表只能当两个值相等时,具体API解释可参考MDN官网。
⑪关闭数据库
当数据库操作完毕后,建议关闭它,节约资源。
function closeDB(db) {
db.close();
console.log("数据库已关闭");
}
⑫删除数据库
function deleteDBAll(dbName){
console.log(dbName);
let deleteRequest = window,indexedDB.deleteDatabase(dbName);
deleteRequest.onerror = function (event) {
console.log("删除失败");
};
deleteRequest.onsuccess = function (event) {
console.log("删除成功");
};
}
总结:
IndexedDB 是 HTML5 标准中定义的一种在浏览器端存储大量结构化数据的 API,它具备以下特点:
- 异步 API:IndexedDB 采用异步 API 进行访问和操作,能够更好地应对大量数据存储、高并发数据访问等需求。
- 大规模数据存储:IndexedDB 支持存储大量的结构化数据,并支持索引和事务处理等高级操作,适用于需要高性能本地存储大规模数据的场景。
- 数据库查询:IndexedDB 支持复杂的查询,包括范围查询和游标查询等,可帮助开发者进行精确查询和高效数据检索。
- 数据库设计:IndexedDB 本身是基于 NoSQL 的键值存储数据库,和其他数据库(如 MongoDB)的设计理念相似,能够帮助开发者进行灵活的数据库设计和数据模型设计。
相比于其他的浏览器端存储技术,IndexedDB 具有更好的跨平台兼容性、安全性和扩展性,可以满足现代 Web 应用对于数据存储的各种要求。但是,由于其使用较为复杂,需要开发者深入了解浏览器端存储技术、JavaScript 异步编程、数据库设计和查询等方面才能够充分发挥其优势。