一、概述随着浏览器功能的不断增强,越来越多的网站开始考虑在客户端存储大量数据,这样可以减少从服务器获取数据的次数,获取数据直接从本地数据。现有的浏览器数据存储方案不适合存储大量数据:cookie的大小不超过4KB,每次请求都会发回服务器;提供了搜索功能,但无法创建自定义索引。因此需要一个新的解决方案,这就是IndexedDB诞生的背景。通俗地说,IndexedDB是浏览器提供的本地数据库,可以通过web脚本来创建和操作。IndexedDB允许存储大量数据,提供查找接口和构建索引。这些在LocalStorage中不可用。就数据库类型而言,IndexedDB不是关系型数据库(不支持SQL查询语句),更接近于NoSQL数据库。IndexedDB具有以下特点。键值对存储。IndexedDB内部使用一个对象库(objectstore)来存储数据。所有类型的数据都可以直接存储,包括JavaScript对象。在对象仓库中,数据以“键值对”的形式存储。每条数据记录都有对应的主键。主键是唯一的,不能重复,否则会报错。异步。IndexedDB在运行过程中不会锁定浏览器,用户仍然可以进行其他操作,这与LocalStorage的操作是同步的相反。异步设计是为了防止大量数据的读写,拖慢网页的性能。支持交易。IndexedDB支持事务,也就是说一系列的操作步骤只要有一步失败,整个事务就会被取消,数据库会回滚到事务发生前的状态,不会出现只有部分数据被重写。同源限制IndexedDB受同源限制,每个数据库都对应创建它的域名。网页只能访问自己域名下的数据库,不能访问跨域数据库。存储空间大IndexedDB的存储空间远大于LocalStorage,一般不低于250MB,甚至没有上限。支持二进制存储。IndexedDB不仅可以存储字符串,还可以存储二进制数据(ArrayBuffer对象和Blob对象)。二、基本概念IndexedDB是一个比较复杂的API,涉及的概念很多。它将不同的实体抽象为对象接口。学习这个API就是学习它的各种对象接口。数据库:IDBDatabase对象对象仓库:IDBObjectStore对象索引:IDBIndex对象事务:IDBTransaction对象操作请求:IDBRequest对象指针:IDBCursor对象主键集合:IDBKeyRange对象下面是一些主要的概念。数据库数据库是相关数据容器的集合。每个域名(严格来说是协议+域名+端口)可以创建任意数量的数据库。IndexedDB数据库有版本的概念。同时,只有一个版本的数据库存在。如果要修改数据库结构(添加或删除表、索引或主键),只能通过升级数据库版本来完成。对象存储每个数据库都包含若干个对象存储(objectstore)。它类似于关系数据库中的表。数据记录对象仓库存储数据记录。每条记录类似于关系数据库中的一行,但只有两部分:主键和数据体。主键用于创建默认索引,必须不同,否则会报错。主键可以是数据记录中的一个属性,也可以指定为一个递增的整数。{id:1,text:'foo'}在上面的对象中,id属性可以用作主键。数据体可以是任何数据类型,不限于对象。为了加快数据检索速度,可以针对对象仓库中的不同属性建立索引。事务性数据记录的读写和删除,必须通过事务来完成。事务对象提供了error、abort和complete三个事件,用于监控运行结果。3、操作流程IndexedDB数据库的各种操作一般按照以下流程进行。本节仅给出简单的代码示例以供快速入门。每个对象的详细API请看这里。打开数据库使用IndexedDB的第一步是使用indexedDB.open()方法打开数据库。varrequest=window.indexedDB.open(数据库名称,版本);该方法接受两个参数,第一个参数是一个字符串,表示数据库的名称。如果指定的数据库不存在,将创建一个新的数据库。第二个参数是一个整数,表示数据库的版本。如果省略,打开现有数据库时,默认为当前版本;创建新数据库时,默认为1。indexedDB.open()方法返回一个IDBRequest对象。该对象通过error、success、upgradeneeded三个事件处理打开数据库的操作结果。错误事件错误事件表示打开数据库失败。request.onerror=function(event){console.log('数据库打开错误报告');};成功事件成功事件表示数据库打开成功。vardb;request.onsuccess=function(event){db=request.result;console.log('数据库打开成功');};这时候通过request对象的result属性获取数据库对象。upgradeneeded事件如果指定的版本号大于数据库的实际版本号,将发生数据库升级事件upgradeneeded。vardb;request.onupgradeneeded=function(event){db=event.target.result;}这时候通过event对象的target.result属性获取数据库实例。创建新数据库创建新数据库与打开数据库的操作相同。如果指定的数据库不存在,将创建它。不同的是,后续的操作主要是在upgradeneeded事件的监听函数中完成的,因为此时版本是从头开始创建的,所以会触发这个事件。通常,创建一个新的数据库后,首先要做的是创建一个新的对象存储(即创建一个新表)。request.onupgradeneeded=function(event){db=event.target.result;varobjectStore=db.createObjectStore('person',{keyPath:'id'});}上面代码中,数据库创建成功后,添加新建一张person表,主键是id。比较好的写法是先判断这个表是否存在,不存在则新建一个。request.onupgradeneeded=function(event){db=event.target.result;varobjectStore;if(!db.objectStoreNames.contains('person')){objectStore=db.createObjectStore('person',{keyPath:'id'});}}主键(key)是默认索引的属性。比如数据记录是{id:1,name:'张三'},那么id属性就可以作为主键。主键也可以指定为下一级对象的一个??属性,比如{foo:{bar:'baz'}}的foo.bar也可以指定为主键。如果数据记录中没有适合作为主键的属性,可以让IndexedDB自动生成一个主键。varobjectStore=db.createObjectStore('person',{autoIncrement:true});在上面的代码中,指定的主键是一个递增的整数。对象仓库创建好后,下一步就是创建索引。request.onupgradeneeded=function(event){db=event.target.result;varobjectStore=db.createObjectStore('person',{keyPath:'id'});objectStore.createIndex('name','name',{unique:false});objectStore.createIndex('email','email',{unique:true});}上面代码中,IDBObject.createIndex()的三个参数分别是索引名,索引所在的属性位于,以及配置对象(指示属性是否包含重复值)。添加新数据添加新数据是指将数据记录写入对象仓库。这需要通过交易来完成。functionadd(){varrequest=db.transaction(['person'],'readwrite').objectStore('person').add({id:1,name:'张三',age:24,email:'zhangsan@example.com'});request.onsuccess=function(event){console.log('数据写入成功');};request.onerror=function(event){console.log('数据写入失败');}}添加();上述代码中,写入数据需要创建一个新的事务。创建新表时,必须指定表名和操作模式(“只读”或“读写”)。创建新事务后,通过IDBTransaction.objectStore(name)方法获取IDBObjectStore对象,通过表对象的add()方法向表中写入一条记录。写操作是一个异步操作。通过监听connection对象的success事件和error事件,我们可以知道写入是否成功。读取数据读取数据也是通过事务完成的。functionread(){vartransaction=db.transaction(['person']);varobjectStore=transaction.objectStore('person');varrequest=objectStore.get(1);request.onerror=function(event){console.log('交易失败');};request.onsuccess=function(event){if(request.result){console.log('姓名:'+request.result.name);console.log('年龄:'+request.result.age);console.log('Email:'+request.result.email);}else{console.log('没有获取数据记录');}};}read();上述代码中,objectStore的.get()方法用于读取数据,参数为主键的值。遍历数据,遍历数据表的所有记录,使用指针对象IDBCursor。functionreadAll(){varobjectStore=db.transaction('person').objectStore('person');objectStore.openCursor().onsuccess=function(event){varcursor=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('Nomoredata!');}};}readAll();在上面的代码中,新建指针对象的openCursor()方法是异步操作,所以监听成功事件。更新数据要更新数据,请使用IDBObject.put()方法。functionupdate(){varrequest=db.transaction(['person'],'readwrite').objectStore('person').put({id:1,name:'Lisi',age:35,email:'lisi@example.com'});request.onsuccess=function(event){console.log('数据更新成功');};request.onerror=function(event){console.log('数据更新失败');}}更新();上面代码中put()方法自动更新主键为1的记录。删除数据IDBObjectStore.delete()方法用于删除记录。functionremove(){varrequest=db.transaction(['person'],'readwrite').objectStore('person').delete(1);request.onsuccess=function(event){console.log('数据删除成功');};}消除();使用索引索引的意义在于,它可以让你搜索任何字段,也就是说,可以从任何字段中获取数据记录。如果不创建索引,则默认只能查找主键(即从主键获取值)。假设在创建新表时,在名称字段上创建索引。objectStore.createIndex('name','name',{unique:false});现在,您可以从名称中找到相应的数据记录。vartransaction=db.transaction(['person'],'readonly');varstore=transaction.objectStore('person');varindex=store.index('name');varrequest=index.get('李四');request.onsuccess=function(e){varresult=e.target.result;if(result){//...}else{//...}}
