一、为什么要使用IndexedDB
随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量的数据存储在客户端,因为能减少从服务器获取数据,可以直接从本地获取数据,但是现有几种浏览器数据存储方案都不适合存储大量数据,而Indexeded就可以大量存储数据,下面就先讲讲现有的几种存储方案以及他们区别。
二:浏览器端存储方案介绍
1.1 方案介绍
1.1.1 Cookie
就是小甜饼的意思,所以它容量非常的小,大小限制为4K左右,是网景公司前雇员Lou Montulli在1993年3月的发明,它主要是保存登录信息,比如你登录某个网站时通常可以看到“记住密码”,这就是通过在cookie中存入一段辨别用户身份的数据来实现的
1.1.2 Web Storage
Web Storage 包括localSortage、sessionStorage
1、 localSortage:是HTML5标准中新加入的技术,并不是划时代的新东西,在 IE 6 时代,就有一个叫 userData 的东西用于本地存储,而当时考虑到浏览器兼容性,更通用的方案是使用 Flash。而如今,localStorage 被大多数浏览器所支持。
2、sessionStorage: 只存在于当前tab页面,刷新页面数据依旧存在。但当页面关闭后,sessionStorage 中的数据就会被清空
1.1.3 IndexedDB
就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。
这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
1.2 方案区别
三:IndexedDB应用场景
1.1 应用场合
1、需要存储大量结构化数据得项目
2、有些需要在线编辑的项目
1.2 实际项目应用
1、北京优锘科技旗下的ThingMap上的地图的瓦片、建筑、河流、各种图层等等都是通过indexedDB存储的(thingmap.thingjs.com/theme)
2、谷歌、百度地图相关的一些3D对象数据
3、京东、腾讯课堂有些数据同样存的indexedDB
4、ThingJS - 物联网3D可视化PaaS平台 - 数字孪生可视化平台 里面的在线开发代码也是存的indexedDB(www.thingjs.com/guide/)
四:IndexedDB特性
1.1 可存储的内容
IndexedDB内部采用对象仓库(object store)存放数据。什么意思呢,就是可以存放所有类型的数据(字符串、数字、对象、数组、Date、文件、甚至还可以存储二进制数据如:ArrayBuffer 对象和 Blob 对象),包括JS对象,直接存储对象不需要像localStorage做序列化操作,对象仓库中,数据以“键值对”的方式保存,每一条数据记录都有主键,是独一无二的,重复会抛出错误
1.2 异步
整个操作是异步的,localStorage是同步的,获取数据会阻塞程序的运行,而IndexedDB获取数据会类似于一个Promise这样一种异步的操作,异步设计是为了防止大量数据的读写,拖慢网页。
1.3 支持事务
IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
1.4 同源限制
IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,不能访问跨域的数据库。
1.5 存储空间大
IndexedDB 的储存空间比 LocalStorage 大得多,一般可达到500M,甚至没有上限。
上面那张图说的就是不同的前端存储方案的容量对比,indexedDB可以达到五百兆,不同的浏览器存储容量会有所不同,有的项目在浏览器里面可能会达到1G,但是这时候会比较卡,这也算是缺点之一,所以不是所有的场景都使用IndexedDB。
五:IndexedDB操作步骤
IndexedDB 数据库的各种操作,一般是按照下面的流程进行的。这个部分只给出简单的代码示例,用于快速上手,详细的各个对象的 API 请看这里(wangdoc.com/javascript/…
在线 代码编辑地址:codepen.io/qswzch/pen/…
1.1 打开数据库
1.2 新建数据库
新建数据库与打开数据库是同一个操作。如果指定的数据库不存在,就会新建。不同在于,后续的操作主要在upgradeneeded事件的监听函数里面完成,因为这时版本从无到有,所以会触发这个事件。
createObjectStore方法存放数据的对象仓库,类似于传统关系型数据库的表格,返回一个 IDBObjectStore 对象。该方法只能在versionchange事件监听函数中调用。
1.3 新增数据
新增数据指的是向对象仓库写入数据记录,这需要通过事务完成。
transaction()方法会返回IDBTransaction 对象用来异步操作数据库事务,所有的读写操作都要通过这个对象进行
写入操作是一个异步操作,通过监听连接对象的success事件和error事件,了解是否写入成功。
1.4 读取数据
读取数据也是通过事务完成
1.5 遍历数据
遍历数据表格的所有记录,要使用指针对象 IDBCursor。
1.6 更新数据
更新数据要使用IDBObject.put()方法。
1.7 删除数据
IDBObjectStore.delete()方法用于删除记录。
1.8 使用索引
索引的意义在于,可以让你搜索任意字段,也就是说从任意字段拿到数据记录。如果不建立索引,默认只能搜索主键(即从主键取值)。
上面得代码中分别对name和email建立了索引
1.9 完整代码
/**
* 打开数据库
* open这个方法接受两个参数
* 第一是数据库得名字,如果指定的数据库不存在就会新建
* 第二个是数据库版本号,如果不传,打开已有数据库时,
* 默认为当前版本,新建数据库时默认为1
*/
const request = indexedDB.open('dataBase1',1)
let db; //用于存储数据库
// open() 方法返回一个IDBRequest对象,这个对象会通过三种事件处理打开数据库的操作结果
// error事件表示打开数据库失败
request.onerror = (event) => {
console.log('数据库打开报错');
}
// success事件表示成功打开数据库。
request.onsuccess = (event) => {
// 通过request.result属性拿到数据库对象
db = request.result;
console.log(db);
console.log('数据库打开成功');
add()
read()
update()
remove()
readAll()
indexUsing()
}
// upgradeneeded事件表示如果指定得版本号大于数据库的实际版本号,会发生数据库升级事件
// 新增数据库后续操作
request.onupgradeneeded = (event) => {
// 如果数据库不存在就新建,新建的后续操作在该方法中完成
db = event.target.result; // 拿到数据库实例
let objectStore
if (!db.objectStoreNames.contains('person')) {
// 新增一个person表, 主键是id, 主键是默认建立索引的属性
// 主键也可以指定为下一层对象的属性,比如{ foo: { bar: 'baz' } }的foo.bar也可以指定为主键。
objectStore = db.createObjectStore('person',{ keyPath: 'id' })
// 如果数据记录里面没有合适作为主键的属性,那么可以让 IndexedDB 自动生成主键
// objectStore = db.createObjectStore('person',{ autoIncrement: true })
// 新建索引,createIndex三个参数是索引名称、索引所在属性、配置对象(表明该属性是否包含重复的值)
objectStore.createIndex('name','name',{unique: false})
objectStore.createIndex('eamil','eamil',{unique: true})
}
}
// 新增数据
function add(){
// 新建一个事务,指定表格名称和操作模式,操作模式分为读写和只读
// 事务创建完成会通过IDBTransaction.objectStore(name)方法拿到IDBObjectStore 表格对象
// 通过表格对象的add方法向表格写入记录
const request = db.transaction(['person'],'readwrite').objectStore('person').add({
id: 3, name: '王五', age: 27, email: 'wangwu@example.com'
})
request.onsuccess = (event) => {
console.log(event)
console.log('数据写入成功');
}
request.onerror = (event) => {
console.log(event)
console.log('数据写入失败');
}
}
// 读取数据
function read() {
const transaction = db.transaction(['person'])
const objectStore = transaction.objectStore('person')
// 用于读取数据,参数是主键的值
const request = objectStore.get(3)
// 失败
request.onerror = (event) => {
console.log('事务失败');
};
// 成功
request.onsuccess = (event) => {
const rslt = request.result
if (rslt) {
console.log('name:' + rslt.name)
console.log('age:' + rslt.age)
console.log('email:' + rslt.email)
}else {
console.log('未获得数据记录');
}
}
}
// 遍历数据
function readAll() {
const transaction = db.transaction(['person'])
const objectStore = transaction.objectStore('person')
// IDBCursor 对象代表指针对象,用来遍历数据仓库(IDBObjectStore)或索引(IDBIndex)的记录
// IDBCursor对象是个异步对象,需要监听success
objectStore.openCursor().onsuccess = (event) => {
const 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.email)
cursor.continue()
}else {
console.log('没有更多数据了!');
}
}
}
// 更新数据
function update(){
const transaction = db.transaction(['person'],'readwrite')
const objectStore = transaction.objectStore('person')
// 使用put方法,自动更新主键为1得记录
const request = objectStore.put({
id: 1,
name: 'lisan',
age: 35,
email: 'lisan@example.com'
})
request.onsuccess = (event) => {
console.log('数据更新成功');
};
request.onerror = (event) => {
console.log('数据更新失败');
}
}
// 删除数据
function remove () {
const transaction = db.transaction(['person'],'readwrite')
const objectStore = transaction.objectStore('person')
const request = objectStore.delete(1)
request.onsuccess = (event) => {
console.log('数据删除成功')
}
}
// 使用索引
function indexUsing(){
const transaction = db.transaction(['person'],'readonly')
const store = transaction.objectStore('person')
// index()方法返回指定名称的索引对象 IDBIndex
const index = store.index('name')
const request = index.get('王五')
request.onsuccess = (event) => {
const rslt = event.target.result
if(rslt){
console.log(rslt)
console.log('Id:' + rslt.id)
console.log('Name:' + rslt.name)
console.log('Age:' + rslt.age)
console.log('Email:' + rslt.email)
}else {
console.log('没有查到!');
}
}
}