indexedDB用于在浏览器端存储大量的结构化数据。对比于其余的浏览器存储技术(cookie,localStorage),indexedDB具备如下优势:数据库
1.存储空间特别大,远超cookie与localStorage; 2.能够经过索引实现高性能搜索; 3.全部操做彻底异步执行;
另外,indexedDB还具有如下特色:数组
1.非关系型数据库,不能使用结构化查询语言(SQL); 2.数据以健值对的方式存储; 3.事务模式数据库,任何操做都发生在事务中; 4.遵循同源策略;
基于以上的特色,indexedDB很是适合于须要在客户端存储大量数据的网站,更是和基于PWA的WebAPP相契合。
关于indexedDB的浏览器兼容性,能够参照:https://www.caniuse.com/#search=indexeddb浏览器
var request = window.indexedDB.open(DBName, version);
函数包含两个参数,第一个为数据库名称,第二个为数据库版本号(整数)。
使用open
函数建立/打开数据库时,根据参数不一样,可能存在如下几种状况:缓存
1.指定名称的数据库不存在,则新建数据库,默认版本号为1; 2.指定名称的数据库已存在,省略版本号,则打开当前版本号的数据库; 3.指定名称的数据库已存在,版本号大于数据库实际版本号,则进行数据库升级;
open
函数返回一个IDBOpenDBRequest
对象,该对象经过如下三种事件进行数据库打开操做的后续处理:服务器
var db; //打开数据库成功事件,返回数据库对象实例(IDBDatabase对象的实例) request.onsuccess = function(event){ db = request.result; } //打开数据库失败事件 request.onerror = function(event){ //打印报错信息 } //数据库升级事件,仅在建立数据库/数据库升级时触发 request.onupgradeneeded = function(event){ //缓存IDBDatabase接口,用于后续建立/删除对象存储空间(对象仓库,类比于关系型数据库的表) var _db = event.target.result; //建立/删除对象存储空间的操做 ... }
不管是打开数据库,或者是更新数据库,全部后续的查询、插入、删除等操做,都要经过onsuccess函数返回的IDBDatabase实例去调用。
全部对数据库schema的更新只能在onupgradeneeded事件中进行。cookie
indexedDB中,使用对象仓库来存储具体的数据,其功能相似与关系型数据库的表。
不一样于关系型数据库的表结构,对象仓库中数据的存储结构以下图所示:
Key表明对象仓库主键,而具体的数据,采用对象的方式存储在Value中。异步
在数据库新建操做完成后,接下来就是新建对象仓库,并建立索引了。
对象仓库的建立 与 该对象仓库中索引的建立方法为:数据库设计
request.onupgradeneeded = function(event){ var _db = event.target.result; var objectStore; //首先,能够先判断是否存在重名的对象仓库 if(!_db.objectStoreNames.contains('person')){ //建立对象仓库,第一个参数为仓库名称,第二个参数可选 objectStore = _db.createObjectStore('person', { //主键 keyPath: 'id', //可选,主键自增 autoIncrement: true }); //建立索引,第一个参数为索引名称,第二个参数为索引所在的属性,可使用数组将多个属性设置为一个索引 //第三个参数为可选参数,指定该属性的值是否要求惟一 objectStore.createIndex('phone_index', 'phone', {unique: true}); } }
关于为何要建立索引:对数据库不是很熟悉,或者对关系型数据库有所了解,而对非关系型数据库不太了解的同窗,可能会对索引有所疑惑。索引的意义,在于快速查询数据,就如同书的目录同样,经过目录能够快速找到咱们想看内容的页数。在关系型数据库中,查询数据,可使用SQL语句:select * from person where phone="13910002000",而在非关系型数据库中,没有SQL,若是没有索引,咱们只能根据主键去查询数据。只有创建索引以后,才能将索引对应的属性做为查询条件进行数据查询。
进行数据库版本升级的状况下,以前版本的对象仓库是依然存在的,无需重复建立。若是须要更新对象仓库属性,须要先删除原有对象仓库,再从新建立。函数
indexedDB是事务模式数据库,全部的数据操做,都要在事务中进行。所以在学习数据的增删改查操做以前,咱们先看如何新建事务,并获取IDBObjectStore对象。性能
1.建立事务
var transaction = db.transaction(storeNames, mode);
使用IDBDatabase对象实例来调用事务建立函数,第一个参数,能够是一个对象仓库名称的字符串(仅使用到一个对象仓库),或者是一个对象仓库名称的数组(用到一个或多个对象仓库)。第二个参数,指定事务中能够进行的操做类型,包括readonly
(只读)和readwrite
(可读写)。
2.获取IDBObjectStore对象
var objectStore = transaction.objectStore(storeName);
事务建立结束后,获取须要进行操做的对象仓库——IDBObjectStore对象,在这个对象下,进行具体的增删改查操做。
对于多事务处理的状况,事务在被建立的时候就已经开始了,所以对对象仓库的操做顺序,由事务建立的顺序决定。样例参见https://developer.mozilla.org/zh-CN/docs/Web/API/IDBTransaction
function add(){ //建立事务 var transaction = db.transcation('person', 'readwrite'); //获取IDBObjectStore对象 var objectStore = transaction.objectStore('person'); //执行新增操做并返回IDBRequest对象,若是主键设置为自增,参数可不包含主键 var request = objectStore.add({'name': '张三', 'phone': '13910002000'}); request.onsuccess = function(event){ //插入成功 //返回新增数据的主键 console.log(request.result); } request.onerror = function(event){ //插入失败,失败的缘由能够在event中查看 } } add();
function remove(){ //建立事务 var transaction = db.transcation('person', 'readwrite'); //获取IDBObjectStore对象 var objectStore = transaction.objectStore('person'); //执行删除操做并返回IDBRequest对象,函数的参数为要删除数据的主键,数据类型为Number,或为IDBKeyRange var request = objectStore.delete(1); request.onsuccess = function(event){ //删除成功 } request.onerror = function(event){ //删除失败 } } remove();
查询能够依据主键值进行,或者经过建立的索引进行。
1.经过主键进行查询
function get(){ //建立事务 var transaction = db.transcation('person', 'readonly'); //获取IDBObjectStore对象 var objectStore = transaction.objectStore('person'); //执行查询操做并返回IDBRequest对象,函数的参数为要查询数据的主键,数据类型为Number,或为IDBKeyRange var request = objectStore.get(1); request.onsuccess = function(event){ //查询成功 //返回查询结果,知足查询条件的数据对象 console.log(request.result); } request.onerror = function(event){ //查询失败 } } get();
2.经过索引进行查询
经过建立的索引进行查询时,须要首先经过IDBObjectStore对象打开某个名称的索引,获取IDBIndex对象,再经过IDBIndex对象进行查询操做。
function get(){ //建立事务 var transaction = db.transcation('person', 'readonly'); //获取IDBObjectStore对象 var objectStore = transaction.objectStore('person'); //获取IDBIndex对象,参数为索引的名称 var index = objectStore.index('phone_index'); //执行查询操做并返回IDBRequest对象,函数的参数为要查询数据,或为IDBKeyRange var request = index.get('13910002000'); request.onsuccess = function(event){ //查询成功 //返回查询结果,知足查询条件的数据对象 console.log(request.result); } request.onerror = function(event){ //查询失败 } } get();
查询多条数据时,有两种方式,第一种是经过IDBObjectStore对象或者IDBIndex对象的getAll
方法(indexedDB 2.0),第二种是经过游标进行数据遍历。
1.使用getAll方法
function getAll(){ //建立事务 var transaction = db.transcation('person', 'readonly'); //获取IDBObjectStore对象 var objectStore = transaction.objectStore('person'); //获取IDBIndex对象,参数为索引的名称 var index = objectStore.index('age_index'); //执行查询操做并返回IDBRequest对象 //函数的第一个参数为要查询数据,或为IDBKeyRange,可选,若是不传则返回全部数据 //函数的第二个参数为要返回的数据数量,可选,若是不加则返回全部知足条件的数据,数据类型为Number,整数 var request = index.getAll('20', 10); request.onsuccess = function(event){ //查询成功 //返回查询结果,知足查询条件的数据对象数组 console.log(request.result); } request.onerror = function(event){ //查询失败 } } getAll();
2.使用游标遍历数据
function readAll(){ //建立事务 var transaction = db.transcation('person', 'readonly'); //获取IDBObjectStore对象 var objectStore = transaction.objectStore('person'); //openCursor函数拥有两个可选参数,第一个参数为查询条件 //第二个参数为查询方向,默认为next,还能够设置为prev //项目基本没有使用这种方式遍历数据,因此具体参数设置/是否可使用索引等还没有验证,等之后补充 objectStore.openCursor().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.phone); cursor.continue(); } else { console.log('没有更多数据了!'); } } } readAll();
function update(){ //建立事务 var transaction = db.transcation('person', 'readwrite'); //获取IDBObjectStore对象 var objectStore = transaction.objectStore('person'); //执行更新操做并返回IDBRequest对象,函数的参数要更新的数据,包含主键 var request = objectStore.put({'id': 1, 'name': '张三', 'phone': '13910002000'}); request.onsuccess = function(event){ //更新成功 } request.onerror = function(event){ //更新失败 } } update();
经过以上部分的介绍,咱们能够掌握indexedDB的基本操做,完成一些简单的、基本的业务需求。然而,咱们的业务需求每每不会这么简单,这个部分,根据我在项目中遇到的一些问题,总结一下较为复杂的业务逻辑处理方式。若是各位大拿有更好的处理方式,也但愿不吝赐教。
在上边写的例子中,为了简单的介绍indexedDB操做的各个方法,并无对其进行严谨的封装。在实际生产应用中,增删改查的操做都应该作好封装,方便复用。简单一点的封装能够把数据库对象、要查询条件对象仓库名称、查询条件、查询成功回调和查询失败回调等设置为函数的参数。好一点的方法,能够考虑使用Promise进行相关操做的封装。
全部的数据库操做,都须要等待数据库打开成功的回调,拿到IDBDatabase对象的实例以后,才可以进行操做。对于须要用户去操做的状况,用户须要填写数据,或者填写查询数据,等这些耗时的操做完成时,打开数据库操做通常已经完成了,所以不用作什么特别处理也不会出现问题。
可是在页面初始化就须要拿数据的状况,因为打开数据库的操做是异步的,颇有可能出现数据库打开还未成功,请求数据库数据的函数就已经开始执行了,致使数据获取失败。所以,比较稳妥的方式,应该是当打开数据库这个异步操做成功执行成功回调时,使用EventEmitter等库进行一个事件触发,让监听这个事件的函数知晓indexedDB已经准备完毕,能够进行相关操做。
在关系型数据库中,能够经过SQL语句完成连表查询,而在indexedDB中,因为没有SQL,没法进行连表查询的操做。若是想要查询到更多的数据,目前我只能经过数据冗余去完成。这就要求在设计数据库的时候,提早把相关的功能逻辑考虑到。
在基础操做介绍中,对于对象仓库的增删改查中,尤为是在查询操做,函数的参数出了能够为主键值、具体的查询值等,还能够为一个IDBKeyRange对象实例。经过使用IDBKeyRange,咱们能够把查询条件约束到一个范围中。IDBKeyRange提供的方法和对应的查询值约束范围包括:
IDBKeyRange.upperBound(x) keys <= x IDBKeyRange.upperBound(x, ture) keys < x IDBKeyRange.lowerBound(y) keys >= x IDBKeyRange.lowerBound(y, ture) keys > x IDBKeyRange.bound(x, y) keys >= x && keys <= y IDBKeyRange.bound(x, y, true, ture) keys > x && keys < y IDBKeyRange.bound(x, y, true, false) keys > x && keys <= y IDBKeyRange.bound(x, y, false, true) keys >= x && keys < y IDBKeyRange.only(z) keys = z
使用方法为:
function getAll(){ //建立事务 var transaction = db.transcation('person', 'readonly'); //获取IDBObjectStore对象 var objectStore = transaction.objectStore('person'); //获取IDBIndex对象,参数为索引的名称 var index = objectStore.index('age_index'); //获取年龄大于等于10岁小于等于20岁的数据 var request = index.getAll(IDBKeyRange.bound(10, 20)); request.onsuccess = function(event){ //查询成功 //返回查询结果,知足查询条件的数据对象数组 console.log(request.result); } request.onerror = function(event){ //查询失败 } } getAll();
在关系型数据库中,能够经过如下SQL语句完成多条件查询:select * from person where age = 20 AND name = '张三'
在indexedDB中,没有提供多条件查询的直接方法,想要实现多条件查询,目前一个可行的方法为,建立多属性索引,并借助IDBKeyRange进行查询。
//建立数据仓库时 request.onupgradeneeded = function(event){ var _db = event.target.result; var objectStore; if(!_db.objectStoreNames.contains('person')){ objectStore = _db.createObjectStore('person', { keyPath: 'id', autoIncrement: true }); //建立一个多属性索引 objectStore.createIndex('multi_index', ['name', 'age'], {unique: false}); } } //进行查询时 function getAll(){ var transaction = db.transcation('person', 'readonly'); var objectStore = transaction.objectStore('person'); var index = objectStore.index('multi_index'); var request = index.getAll(IDBKeyRange.only(['张三', 20])); request.onsuccess = function(event){ console.log(request.result); } request.onerror = function(event){ } } getAll();
一旦清楚了浏览器缓存,一切数据都将会被清除。因此,要么核心业务不要过分依赖于indexedDB,要么本身整一套方法机制进行数据的按期向服务器同步,要么。。。你跟用户说一下,不要让他没事清缓存。。。T-T
另外,在隐私模式下,推出浏览器以后,数据也会被清理(没有验证过)。
做为一个以关系型数据库开始学习的人,刚上手去作indexedDB的时候,因为是第一次接触NOSQL,不怎么适应,不少逻辑都已经被关系型数据库和SQL语句给固化了,结果在数据库设计和使用上走了不少弯路,特别蓝瘦。好在我遇到的坑,都已经跳出去了。。。关于indexedDB,个人总结目前就这些了,还想要进一步学习的同窗,本身去查阅文档吧。有存在问题的地方,还望各位大拿指正。