想必前端本得资源化在我们日常工作中也是经常用的一个技术了,其中我们常用的莫过于cookie、Web Storage(sessionStorage、localStorage),但这些本地化存储方式比较明显的缺陷,例如:
1.cookie:大小不超过4kb,每次请求发回服务器
1.web storage:存储量在2.5mb到10mb之间(各家浏览器不同),不提供搜索功能,不能建立自定义索引
随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据存储在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。而现有的浏览器的存储方案,都不适合存储大量数据。所以,需要一种新的解决方案,那就是IndexedDB 诞生的背景。
这时我们需要想到IndexedDB ,在我们浏览器中也可以哦使用数据库这种形式来玩转数据本地化存储。然而IndexedDB 的使用时比较繁琐而复杂的,有一定的学习成本,但是第三方库localForage的出现几乎可以使得我们轻松无负担在浏览器中使用IndexedDB 。
IndexedDB 是一个用于在浏览器中存储大量数据结构的Web API,是浏览器提供的本地数据库,可以被网页脚本创建和操作,允许存储大量数据,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象),提供查找接口,能建立索引。且IndexedDB 不同于其他SQL的关系型数据库,IndexedDB 是一个事务型的数据库系统,会将数据集作为个体对象存储,数据形式使用的是JSON,而不是列数固定的表格来存储数据的。
1.键值对存储:indexDB采用对象仓库存放数据,所有数据类型数据都可以存入,包括JavaScript对象。对象仓库中,数据以“键值对”的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能重复,否则会抛出一个错误。
2.异步:indexDB操作不会堵塞浏览器进程,用户依然可以进行其他操作,这与LocalStorage形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现
3.支持事务:操作步骤中一步失败,整个事务取消,不存在只改写一部分数据的情况
4.同源限制:不能访问跨域数据库,只能访问自身域名下的数据库
5.存储空间大:IndexedDB的存储空间比LocalStorage大很多,一般涞水不少于250MB,甚至没有上线
5.支持二进制存储:可以存储字符串,可以可以存储二进制数据
IndexedDB是一个比较复杂的API,设计不少概念,它把不同的实体,抽象成一个个对象接口。学习这个API,就是学习它的各种对象接口。
- 数据库:IDBDatabase对象
- 对象仓库:IDBObjectStore对象
- 索引:IDBIndex对象
- 事务:IDBTransaction对象
- 操作请求:IDBRequest对象
- 指针:IDBCursor对象
- 主键集合:IDBKeyRange对象
下面是一些主要概念
数据库是一系列相关数据的容器。每个域名(严格来说,是协议+域名+端口)都可以新建任意多个数据库。IndexedDB数据量有版本的概念,同一时刻,只能有一个版本的数据库存在。如果要修改数据库结构(新增或者删除表、索引或者主键),只能同事升级数据库版本完成。
每个数据库包含若干个对象仓库,它类型于关系型数据库的表格
对象仓库保存的是数据记录。每条记录类似于关系型数据库的行,但是只有主键和数据体两部分,主键用来建立默认的索引,必须是不同的,否则会报错。主键可以使数据记录里面的一个属性,也可以指定为一个递增的整数编号。
{ id: 1, text: 'foo' }
上面的对象中,id属性可以作为主键
数据体可以是任意数据类型,不限于对象
为了加速数据的检索,可以在对象仓库里面,为不同的属性建立索引
数据记录的读写和删改,都要通过事务完成。事务对象提供error、abort和complete三个事件,用来监听操作结果。
let request = window.indexedDB.open(databaseName,version)
这个方法接受两个参数,第一个参数是字符串,表示数据库的名字。如果指定的数据库不存在,就会新建数据库。第二个参数是整数,表示数据库的版本。如果省略,打开已有数据库时,默认为当前版本;新建数据库时,默认为1
indexedDB.open()方法返回一个IDBRequest对象,这个对象通过三种事件error、sussess、upgradeneeded,处理打开数据库的操作结果。
error事件表示打开数据库失败
request.onerror = function(event){
console.log('数据库打开报错')
}
success事件表示成功打开数据库
let db;
request.onsuccess = function (event){
db = request.result;
console.log('数据离打开成功')
}
这时,通过request对象的result属性可以拿到数据库对象
如果指定的版本号,大于数据库的实际版本号,就会发生数据库升级事件
let db
request.onupgrapdeneede = function(event){
db = event.targer.result
}
这时通过事件对象的target.result
属性,拿到数据库实例
新建数据库和打开数据库是同一个操作,如果指定的数据不存在,就会新建。不同之处在于,后续的操作主要在upgradeneeded事件的监听函数里面完成,因为这时版本从无到有,所以会触发这个事件。
通常,新建数据库以后,第一件事就是新建数对象仓库(即新建表)
request.onupgradeneeded = function(event){
db = event.target.result
let objectStore = db.createObejctStore('person',{keyPath:'id'})
}
上面的代码中,数据库新建成功之后,新增一张叫做person的表格,主键是id。更好的写法是先判断一下。这张表格是否存在,如果不存在再新建
request.onupgradeneeded = function(event){
db = event.target.result
let objectStore;
if(!db.objectStoreNames.contains('person'){
let objectStore = db.createObejctStore('person',{keyPath:'id'})
}
}
如果数据记录里面没有合适的作为主键的属性,那么可以让indexedDB自动生成主键
let objectStore = db.createObjectStroe(
'person',
{autoIncrement:true}
)
上面的代码中,之地那个主键为一个递增的整数。新建对象仓库以后,下一步可以新建索引
request.onupgradeneeded = function(event){
db = event.target.result
let objectStore;
if(!db.objectStoreNames.contains('person'){
objectStore = db.createObejctStore('person',{keyPath:'id'})
ojectStore.createIndex('name','name',{unique:false})
ojectStore.createIndex('email','email',{unique:true})
}
}
上面的代码中,IDBObejct.createIndex()的三个参数分别为索引名称,索引所在的属性、配置对象(说明该属性是否包含重复的值)
新增数据指的是向对象仓库写入数据记录,这需通过事务完成
function add(){
let request = db.transaction(['person'],'readwrite')
.objectStore('person')
.add({id:1,name:'zhangsan',age:24})
request.onsucess = function (event){
console.log('数据写入成功')
}
request.onerror = function (event){
console.log('数据写入失败')
}
}
上面的代码中,写入数据需要新建一个事务,新建时必须指定表格名称和操作模式(“只读”或者“只写”),新建事务之后,通过通过IDBTransaction.objectStore(name)
方法,拿到IDBObjectStore 对象,再通过表格对象的add()方法,向表格写入一条记录。
写入操作是一个异步操作,听过监听连接对象success事件和error事件,了解是否写入成功。
读取数据也是通过事务完成
function read() {
var transaction = db.transaction(['person']);
var objectStore = transaction.objectStore('person');
var request = objectStore.get(1);
request.onerror = function(event) {
console.log('事务失败');
};
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('未获得数据记录');
}
};
}
read();
上面代码中,objectStore.get()
方法用于读取数据,参数是主键的值。
遍历数据表格的所有记录,要使用指针对象 IDBCursor。
function readAll() {
var objectStore = db.transaction('person').objectStore('person');
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.age);
console.log('Email: ' + cursor.value.email);
cursor.continue();
} else {
console.log('没有更多数据了!');
}
};
}
readAll();
上面代码中,新建指针对象的openCursor()
方法是一个异步操作,所以要监听success
事件。
更新数据要使用IDBObject.put()
方法。、
function update() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.put({ id: 1, name: '李四', age: 35, email: '[email protected]' });
request.onsuccess = function (event) {
console.log('数据更新成功');
};
request.onerror = function (event) {
console.log('数据更新失败');
}
}
update();
上面代码中,put()
方法自动更新了主键为1
的记录。
IDBObjectStore.delete()
方法用于删除记录
function remove() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.delete(1);
request.onsuccess = function (event) {
console.log('数据删除成功');
};
}
remove();
索引的意义在于,可以让你搜索任意字段,也就是说从任意字段拿到数据记录。如果不建立索引,默认只能搜索主键(即从主键取值)。
假定新建表格的时候,对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('李四');
request.onsuccess = function (e) {
var result = e.target.result;
if (result) {
// ...
} else {
// ...
}
}