这是我参与 11 月更文挑战的第 1 天,活动详情查看:2021 最后一次更文挑战
1, 前言
随着浏览器的功能不断增强,越来越多的网站开始考虑将大量的数据存储在客户端,相比后端接口,获取数据更快一些。现有的浏览器存储方案都不适合存储大量的数据。Cookie 的大小不超过 4KB,而且每次请求都会发送到服务器,LocalStorage 在 2.5~10MB 直接,浏览器不同,存储的大小还不一样,而且不提供搜索功能,也不能建立自定义索引,webSQL 大家可以课外了解一下,因为 Web SQL Database 规范已经被废弃,官方文档也解释的很清楚,webSQL 规范底层采用 SQLite 的 SQL 方言,而作为一个标准,这是不可接受的,每个浏览器都有自己的实现以后就很能统一标准了,就像 IE 一样。最后,也是最重要的一个客户端存储大量数据的方案:IndexedDB。
2, webSQL 小结
(1) 创建一个数据库
var db = openDatabase('person', 1, 'person', 0)
第一个参数: 数据库的名称
第二个参数: 版本号
第三个参数: 备注
第三个参数: 大小,默认是5M
(2)创建一个表
db.transaction(tx => {
tx.executeSql('create table if not exists student(id unique, name)')
})
(3) 插入两条数据
db.transaction(tx => {
tx.executeSql('insert into student (id, name) value(?, ?)', [1, '张三']);
tx.executeSql('insert into student (id, name) value (?, ?)', [2, '李四']);
})
(4)查询数据
db.transaction(tx => {
tx.executeSql('select * from student', [], (tx, res) => {
let rows = res.rows;
let len = rows.length
for (var i=0; i< len, i++) {
console.log(rows.item(i))
}
})
})
总结
- webSQL 标准不再更新,关系型数据库,底层 sqlite
- chrome 中容量 5M, 支持同域名不同页面共享
- 版本参数用于控制,如果打开版本与现有版本不一致,则报错
3, IndexedDB 存储
IndexedDB 不属于关系型数据库,不支持 SQL 查询语句,更接近 NoSQL 数据库,是一个基于事务操作的 key-value 型前端数据库。它的大部分 API 都是异步的。
-
键值对存储
IndexedDB 内部采用对象仓库存放数据,所有类型的数据都可以直接存放,包括 JavaScript 对象,每一个数据记录都有对应的主键,主键是独一无二的,不能重复
-
异步
IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,它所有的 API 都是异步操作,异步设计时为零防止大量数据的读写拖慢网页的表现。
-
支持事务(transaction)
支持事务就意味着:在操作 (增删改查) 的过程中,只要有异步失败了,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
-
同源限制
每一个数据库对应创建它的域名,网页只能访问自身域名下的数据库,而不能访问跨域名的数据库
-
存储空间大
IndexedDB 的存储空间一般来说不少于 250M,甚至没有上限
1,IndexedDB 常用概念
IndexedDB 是一个比较复杂的 API,它把不同的实体抽象成一个个对象接口。
*数据库 IDBDatabase 对象
*对象仓库 IDBObjectStore 对象, 类似于关系型数据库中的表
*索引 IDBIndex 对象
*事务 IDBTransaction 对象
*操作请求 IDBRequest 对象
*指针 IDBCursor 对象
*主键集合 IDBKeyRange 对象
注意: IndexedDB 数据库有版本的概念,同一时刻,只能有一个版本的数据库存在,如果要修改数据库结构,只能通过升级数据库版本完成,每一个数据库包含若干个对象仓库,也可以理解为:每一个数据库可以创建多个表, 为了加速数据的检索,可以在对象仓库里面建立索引。
注意:事务
数据记录的读写和删改,都要通过事务完成,事务对象提供了三个回调事件
onerror
onsuccess
onupgradeneeded
2, 操作流程
(1) 打开数据库
使用 IndexedDB 的第一步就是打开数据库,使用 indexedDB.open() 方法
var IDBRequest = window.indexedDB.open(databaseName, version)
第一个参数: 数据库名称, 如果数据库不存在就会创建
第二个参数:表示数据库的版本,如果省略,默认为当前版本,默认为:1
结果indexedDB.open()
方法返回一个 IDBRequest 对象,通过三种事件error
, success
, upgradeneeded
, 处理打开的数据库。
-
onerror
事件表示打开数据库失败IDBRequeset.onerror = funciton(event) { console.log('数据库打开失败') }
-
onsuccess
事件表示打开数据库成功var db IDBRequeset.onsuccess = function(event) { db = IDBRequeset.result console.log('数据库打开成功') }
-
upgrade needed
事件表示:如果指定的版本号,大于数据库的实际版本号,就会发生数据库升级事件upgradeneeded
var db; IDBRequeset.onupgradeneeded = function(event) { db = event.target.result }
(2) 新建数据库
新建数据库和打开数据库是同一个操作, 如果指定打开的数据库不存在,就会新建,因为是新建,这时版本号从无到有,是一个升级过程,所以会触发onupgradeneeded
事件,后续的操作都在这个回调里面执行
IDBRequest.onupgradeneeded = function(event) {
db = event.target.result // 拿到数据库实例
// 判断表格是否存在
var objectStore;
if (!db.objectStoreNames.contains('person')) {
objectStore = db.createOjbectStore('person', { keyPath: 'id'})
}
}
没有 id,也可以让 IndexedDB 自动设置主键
objectStore = db.createOjbectStore('person', { autoIncrement: true })
下一步就可以直接创建索引
objectStore.createIndex('name', 'name', { unique: false})
(3)新增数据
新增数据指的是想对象仓库中写入数据记录,需要通过事务完成
function add() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.add({ id: 1, name: 'zlm', age: 24, email: '[email protected]'});
request.onsuccess = function (event) {
console.log('写入数据成功', event)
};
request.onerror = function (event) {
console.log('写入数据失败',event)
}
}
解读: 添加一个数据,需要新建一个事务,通过 transaction()
方法。 第一个参数:指定表格名称, 第二个参数:操作模式,只读或者读写。新建事务完成之后,通过objectStore()
方法拿到对象仓库,也就是表格对象,一个参数: 表格名称。最后在通过表格对象的add()
方法, 向表格中写入一条记录
(4) 读取数据
读取数据也是通过事务完成
function read() {
var transaction = db.transaction['person'];
var objectStore = transaction.objectStore('person')
var request = objectStore.get(1)
request.onerror = function (event) {
console.log('读取事务失败',event)
}
request.onsuccess = function (event) {
if (request.result) {
console.log('Name', + request.result.name)
console.log('Age', + request.result.age)
console.log('Email', + request.result.email)
} else {
console.log('未获取数据记录')
}
console.log('读取成功事件', event)
}
}
(5) 遍历数据
遍历表格中的所有数据,使用指针对象IDBCursor
function readAll() {
var objectStore = db.transaction('person').objectStore('person')
objectStore.opneCursor().onsuccess = function(event) {
var cursor = event.target.result
if (cursor) {
console.log('ID: ' + cursor.key)
console.log("Name: " + cursor.value.name)
console.log("Age: " + cursor.value.age)
console.log('Email: ' + cursor.value.name)
cursor.continue()
} else {
console.log('没有获取到更多的数据')
}
}
}
(6)更新数据
更新数据要使用IDBObject.put()
方法
function update() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.put({ id: 1, name: '张四', age: 32, email: '[email protected]'})
request.onsuccess = function (event) {
console.log('更新数据成功', event)
}
request.onerror = function (event) {
console.log('没有获取到更多的数据', event)
}
}
(7) 删除数据
使用IDBObjectStore.delete()
方法
function remove() {
var request = db.transaction(['person'])
.objectStore('person')
.delete(1)
request.onsuccess = function (event) {
console.log('删除数据成功', event)
}
}
(8)索引
索引的意义在于:可以让你搜索任意字段,通过任意字段拿到数据记录,如果不建立索引,只能通过搜索主键
-
新建表格的时候,对 name 字段添加索引
objectStore.createIndex('name', 'name', {unique: false})
-
通过
name
找到对应的数据记录var transaction = db.transaction(['person'], 'readonly') var store = transaction.objectStore('person') var index = store.index('name') var request = index.get('zlm') request.onsuccess = function(event) { var result = event.target.result if(result) { console.log('获取到数据') } else { console.log('没有获取数据') } }
下面是自己的测试 Demo.js
/**
* IDBDatabase 数据库
* IDBObjectStore 对象仓库
* IDBIndex 索引
* IDBTransaction 事务
* IDBRequest 操作请求
* IDBCursor 指针
* IDBKeyRang 主键集合
*/
// ! 打开数据库
var db;
var request = window.indexedDB.open('myIndexDB');
request.onerror = function (event) {
console.log('打开数据库报错', event)
}
request.onsuccess = function (event) {
db = request.result
console.log('数据库打开成功', event)
}
// 如果制定的版本,大于数据库的实际版本号,机会发生数据库升级事件
request.onupgradeneeded = function (event) {
db = event.target.result
console.log('数据库升级事件')
var objectStore;
if (!db.objectStoreName.contains('person')) {
objectStore = db.createObjectStore('person', { keyPath: 'id'})
objectStore.createIndex('name', 'name', { unique: true})
objectStore.createIndex('email', 'email', { unique: true})
}
console.log(objectStore)
}
console.log(db)
// 新增数据
function add() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.add({ id: 1, name: '张三', age: 24, email: '[email protected]'});
request.onsuccess = function (event) {
console.log('写入数据成功', event)
};
request.onerror = function (event) {
console.log('写入数据失败',event)
}
}
// 读取数据
function read() {
var transaction = db.transaction['person'];
var objectStore = transaction.objectStore('person')
var request = objectStore.get(1)
request.onerror = function (event) {
console.log('读取事务失败',event)
}
request.onsuccess = function (event) {
if (request.result) {
console.log('Name', + request.result.name)
} else {
console.log('未获取数据记录')
}
console.log('读取成功事件', event)
}
}
// 遍历数据表格中所有的数据,要使用指针对象IDBCursor
function readAll() {
var objectStore = db.transaction('person').objectStore('person')
objectStore.opneCursor().onsuccess = function(event) {
var cursor = event.target.result
if (cursor) {
console.log('ID: ' + cursor.key)
console.log("Name: " + cursor.value.name)
cursor.continue()
} else {
console.log('没有获取到更多的数据')
}
}
}
// 更新数据
function update() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.put({ id: 1, name: '张四', age: 32, email: '[email protected]'})
request.onsuccess = function (event) {
console.log('更新数据成功', event)
}
request.onerror = function (event) {
console.log('没有获取到更多的数据', event)
}
}
// 删除数据
function remove() {
var request = db.transaction(['person'])
.objectStore('person')
.delete(1)
request.onsuccess = function (event) {
console.log('删除数据成功', event)
}
}
4, 实战操作
神游一番之后,发现 indexedDB 很多人进行过封装,对比一番发现:
Dexie.js 对 IndexedDB 的封装,语法简单,可以快速方便的编写代码
总结:dexie.js
是一个对浏览器indexedDB
的包装库,让我们操作 indexedDB 更方便。
(1) 为什么要使用dexie.js
- 原生的 IndexedDB 操作都是在回调中进行的
- 原生所有的操作都需要不断的创建事务,判断表和索引的存在
- 原生不支持批量操作
- 原生的错误需要在每一个失败回调中处理
而对比 dexie.js, 支持链式调用,索引定义方便,而且支持多值索引和复合索引,而且文档完善
(1)安装
npm install dexie
或者
<script src="https://unpkg.com/dexie@latest/dist/dexie.js"></script>
(2)使用
步骤一:获取一个数据库实例
import Dexie from 'dexie';
var db = new Dexie('数据库名称')
步骤二: 定义表结构
db.version(1).stores({
users: "++id, name, age, emial",
students: "++id, &username",
books: "id, author, name, *categories"
})
注意:1 如果属性前面有++
符合, 说明是自增的主键
注意:2 如果字段前面有&
符合,说明是唯一索引
注意:3 如果字段前面有*
符合,说明该字段是多值索引
符合 | 描述 |
---|---|
++ | 自动递增主键 |
& | 唯一主键 |
* | 多值索引 |
+ | 复合索引 |
(3) 查询
所有 where 子句运算符都可用于查询多条目索引对象,
-
above() 返回指定字段的值大于某个值的所有记录
-
aboveOrEqual() 返回指定字段值大于等于某个值的所有记录
-
anyOf() 返回数据集中包含在给定数组中的记录
-
below() 返回指定字段的值小于某个值的所有记录
-
belowOrEqual() 返回指定字段值小于等于某个值的所有记录
-
between() 返回介于两者之间的记录
-
equals() 等于
详细的大家可以参考官网文档
下面是自己测试的 Demo.html
<!doctype html>
<html>
<head>
<script src="https://unpkg.com/dexie@latest/dist/dexie.js"></script>
<script>
// 创建数据库病拿到实例化对象
async function openIDB() {
var db = new Dexie("MyDatabase");
db.version(1).stores({
tasks: '++id, name, age, email'
});
// 增加数据
db.tasks.add({
name: 'zlm',
age: 22,
email: '[email protected]'
})
db.tasks.add({
name: '张三',
age: 18,
email: '[email protected]'
})
// 修改数据
db.tasks.put({
id: 2,
name: '李四',
age: 100,
email: ''
})
// 查询数据
console.log('查询第一条数据')
const oldTask = await db.tasks.get(1)
console.log(oldTask)
// 高级查询,查询所有ID大于0的
const allTask = await db.tasks.where('id').above(0).toArray()
console.log('查询所有的数据')
console.log(allTask)
// 批量获取数据
const bulkgetData = await db.tasks.bulkGet([1,2,3,4])
console.log('批量数据')
console.log(bulkgetData)
// db.tasks.clear()
}
openIDB()
</script>
</head>
</html>