IndexedDB是用于客户端的大量的结构化数据存储和使用索引高效率搜索数据的API,它是基于W3C拟定的草案索引数据库的API。相对DOM存储的小存储数据量,IndexedDB具有大容量的数据存储功能,它分别为同步数据和异步数据提供的API,但目前只有异步数据的API在Gecko2.0 上实现。
一、 概述
1. IndexedDB存储为键值对:它可以存储一些复杂的对象,而键可以存储这些对像的属性值,并且可以使用索引对对象的属性的快速检索。
2. IndexedDB建立在交互数据库模型的基础上:任何对IndexedDB的操作都发生一个交互操作(transaction),如它提供的索引、表、游标等均与一个transaction关联,它定义了交互的生存时间与结束时抛出的事件,这样能很好的处理web程序在不同的tab窗口中实例的互操作。
3. IndexedDB的API大多是异步的:你可以向数据库发出操作的“请求”,当操作完成时会产生一个DOM事件,通过事件的类型会知道操作是否成功。
4. IndexedDB使用“请求”机制:操作对象会接收到DOM的success和failure事件,它也有相应的onsuccess和onerror的属性;对象还有readyState、result和errorCode属性来查看当前“请求”的状态,而result属性则根据不同的“请求”返回不同的结果。
5. IndexedDB 使用DOM事件机制来处理“请求”的结果:DOM事件的type属性提示操作是否成功,target属性指向发生“请求”的对象(大多数情况下是IDBRequest对象)。
6. IndexedDB工作基本模式:
0 创建一个交互操作对象
1 发送操作“请求”
2 通过监听DOM事件等待操作完成
3 处理“请求”结果
二、 打开数据库
IndexedDB的操作对象是以moz开头,如我们打开一个数据库如下:
1 |
var request = mozIndexedDB.open( "MyTestDatabase" ); |
mozIndexedDB对象只有一个open方法,它的参数即为数据库的名称,它返回一个IDBRequest对象。接下来要做的就是为request添加onsuccess和onerror事件的处理,它们分别在返回的DOM事件的type为success和error时调用,
01 |
request.onerror = function (event) { |
02 |
03 |
// Do something with request.errorCode! |
04 |
05 |
}; |
06 |
07 |
request.onsuccess = function (event) { |
08 |
09 |
// Do something with request.result! |
10 |
11 |
}; |
IndexedDB采用最小化的错误事件处理,你不会看到很多类型的错误,它只提供一个错误的事件,可以通过event.target.errorCode来查看错误的信息,通常大多的错误都是用户不允许web操作本地的数据库,远程web所拥有的权限问题。
三、 设置数据库的version
当创建数据库之后,需要添加数据,IndexedDB采用对象存储。首先要检查数据库的版本,若不是所期望的值,就要调用setVerion()方法来设置它的版本,如:
01 |
if (db.version != "1.0" ) { |
02 |
03 |
var request = db.setVersion( "1.0" ); |
04 |
05 |
request.onerror = function (event) { |
06 |
07 |
// Handle errors. |
08 |
09 |
}; |
10 |
11 |
request.onsuccess = function (event) { |
12 |
13 |
// Set up the database structure here! |
14 |
15 |
}; |
16 |
17 |
} |
IndexedDB存储的每一个对象均与一个key 关联,关于key 的获取方法参见()。同时我们还可以为对你的存储创建一个Index来查看存储对象部分属性值,如存储人的信息的数据库,我们希望保证不同的人拥有不同的email,就可以使用index和unique flag来设置,如:
01 |
// This is what our customer data looks like. |
02 |
03 |
const customerData = [ |
04 |
05 |
{ ssn: "444-44-4444" , name: "Bill" , age: 35, email: "[email protected]" }, |
06 |
07 |
{ ssn: "555-55-5555" , name: "Donna" , age: 32, email: "[email protected]" } |
08 |
09 |
]; |
10 |
11 |
var request = db.setVersion( "1.0" ); |
12 |
13 |
request.onerror = function (event) { |
14 |
15 |
// Handle errors. |
16 |
17 |
}; |
18 |
19 |
request.onsuccess = function (event) { |
20 |
21 |
// Create an objectStore to hold information about our customers. We're |
22 |
23 |
// going to use "ssn" as our key path because it's guaranteed to be |
24 |
25 |
// unique. |
26 |
27 |
var objectStore = db.createObjectStore( "customers" , { keyPath: "ssn" }); |
28 |
29 |
// Create an index to search customers by name. We may have duplicates |
30 |
31 |
// so we can't use a unique index. |
32 |
33 |
objectStore.createIndex( "name" , "name" , { unique: false }); |
34 |
35 |
// Create an index to search customers by email. We want to ensure that |
36 |
37 |
// no two customers have the same email, so use a unique index. |
38 |
39 |
objectStore.createIndex( "email" , "email" , { unique: true }); |
40 |
41 |
// Store values in the newly created objectStore. |
42 |
43 |
for (i in customerData) { |
44 |
45 |
objectStore.add(customerData[i]); |
46 |
47 |
} |
48 |
49 |
}; |
creatObjectStore()方法和createIndex()方法都有一个可选的对象选项来区分是创建数据库还是索引。creatObjectStore()方法会请求“customers”创建存储对象,并以ssn属性为存储对象的键值,任何试图存储进数据库的对象都需要有ssn属性;我们也可以通name的这个Index来查看存储对象,但对于没有name属性的对象将不会显示出来。
向数据库中添加数据
四、 在添加数据之前,需要先创建一个transaction,创建的方法有三个参数,后两个为可选的,第一个为要关联的数据库名称数组,第二个为打开此数据库的方式(如只读),若无则打开的方式为只读,如:
var transaction = db.transaction(["customers"],IDBTransaction.READ_WRITE);
一个transaction生存时间是与DOM 事件相关联的,如果创建它之后并在返回的事件中没有使用它,就会消亡,唯一让它处理激活状态的就去是使用“请求”机制,当一个请求完成后,在它的回调函数中继续请求,否则transaction就是会消亡。一个transaction有三个事件,为onerror、onsuccess和onabort,一个简单的例子:
01 |
// Do something when all the data is added to the database. |
02 |
03 |
transaction.oncomplete = function (event) { |
04 |
05 |
alert( "All done!" ); |
06 |
07 |
}; |
08 |
09 |
transaction.onerror = function (event) { |
10 |
11 |
// Don't forget to handle errors! |
12 |
13 |
}; |
14 |
15 |
var objectStore = transaction.objectStore( "customers" ); |
16 |
17 |
for ( var i in customerData) { |
18 |
19 |
var request = objectStore.add(customerData[i]); |
20 |
21 |
request.onsuccess = function (event) { |
22 |
23 |
// event.target.result == customerData[i].ssn |
24 |
25 |
}; |
26 |
27 |
} |
五、 从数据库中删除数据
删除数据很简单,如下:
01 |
var request = db.transaction([ "customers" ], IDBTransaction.READ_WRITE) |
02 |
03 |
.objectStore( "customers" ) |
04 |
05 |
. delete ( "444-44-4444" ); |
06 |
07 |
request.onsuccess = function (event) { |
08 |
09 |
// It's gone! |
10 |
11 |
}; |
六、 数据库中取数据
使用get()方法,参数为存储对象的key,如:
1 |
db.transaction( "customers" ).objectStore( "customers" ).get( "444-44-4444" ).onsuccess = function (event) { |
2 |
3 |
alert( "Name for SSN 444-44-4444 is " + event.target.result.name); |
4 |
5 |
}; |
七、 使用游标
使用get()方法需要知道存储对象的key值,但若不知道key值,要看存储对象,就可以使用游标,如下:
01 |
var objectStore = db.transaction( "customers" ).objectStore( "customers" ); |
02 |
03 |
objectStore.openCursor().onsuccess = function (event) { |
04 |
05 |
var cursor = event.target.result; |
06 |
07 |
if (cursor) { |
08 |
09 |
alert( "Name for SSN " + cursor.key + " is " + cursor.value.name); |
10 |
11 |
cursor. continue (); |
12 |
13 |
} |
14 |
15 |
else { |
16 |
17 |
alert( "No more entries!" ); |
18 |
19 |
} |
20 |
21 |
}; |
openCursor()方法有许多参数,首先你可设置遍历的Key的范围,其次可以设置游标遍历的方向。Continue();表示继续遍历。
八、 使用索引
在数据库中,所有的数据都是以SSN以key值来存储的,若要通过name等其他属性查看存储对象,需要遍历每个SSN并将它的name提取出判断是否为要查看的对象,但可以通过index而更为简单的实现,如:
1 |
var index = objectStore.index( "name" ); |
2 |
3 |
index.get( "Donna" ).onsuccess = function (event) { |
4 |
5 |
alert( "Donna's SSN is " + event.target.result.ssn); |
6 |
7 |
}; |
我们还可以通过index使用cursor来遍历存储的数据,并根据不同的cursor打开方式,返回不同的遍历结果,如下两种方式:
01 |
index.openCursor().onsuccess = function (event) { |
02 |
03 |
var cursor = event.target.result; |
04 |
05 |
if (cursor) { |
06 |
07 |
// cursor.key is a name, like "Bill", and cursor.value is the whole object. |
08 |
09 |
alert( "Name: " + cursor.key + ", SSN: " + cursor.value.ssn + ", email: " + cursor.value.email); |
10 |
11 |
cursor. continue (); |
12 |
13 |
} |
14 |
15 |
}; |
16 |
17 |
index.openKeyCursor().onsuccess = function (event) { |
18 |
19 |
var cursor = event.target.result; |
20 |
21 |
if (cursor) { |
22 |
23 |
// cursor.key is a name, like "Bill", and cursor.value is the SSN. |
24 |
25 |
// No way to directly get the rest of the stored object. |
26 |
27 |
alert( "Name: " + cursor.key + ", " SSN: " + cursor.value); |
28 |
29 |
cursor. continue (); |
30 |
31 |
} |
32 |
33 |
}; |
九、 关于游标遍历的范围和方向
如果想要限制游标的遍历范围,可以使用“key range”的对象,并将它做为openCursor()和openKeyCursor()的第一个参数,这样的范围可以是单个键值、或是一个最低边界和最高边界的范围,并规定是否包括范围,如下:
01 |
// Only match "Donna" |
02 |
03 |
var singleKeyRange = IDBKeyRange.only( "Donna" ); |
04 |
05 |
// Match anything past "Bill", including "Bill" |
06 |
07 |
var lowerBoundKeyRange = IDBKeyRange.lowerBound( "Bill" ); |
08 |
09 |
// Match anything past "Bill", but don't include "Bill" |
10 |
11 |
var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound( "Bill" , true ); |
12 |
13 |
// Match anything up to, but not including, "Donna" |
14 |
15 |
var upperBoundOpenKeyRange = IDBKeyRange.upperBound( "Donna" , true ); |
16 |
17 |
//Match anything between "Bill" and "Donna", but not including "Donna" |
18 |
19 |
var boundKeyRange = IDBKeyRange.bound( "Bill" , "Donna" , false , true ); |
20 |
21 |
index.openCursor(boundKeyRange).onsuccess = function (event) { |
22 |
23 |
var cursor = event.target.result; |
24 |
25 |
if (cursor) { |
26 |
27 |
// Do something with the matches. |
28 |
29 |
cursor. continue (); |
30 |
31 |
} |
32 |
33 |
}; |
另外,还可以规定游标遍历的方向,默认的是上升的方向,若使用相反的方向,可以将PREV作为openCursor()或是openKeyCursor()的第二个参数,如下:
01 |
objectStore.openCursor( null , IDBCursor.PREV).onsuccess = function (event) { |
02 |
03 |
var cursor = event.target.result; |
04 |
05 |
if (cursor) { |
06 |
07 |
// Do something with the entries. |
08 |
09 |
cursor. continue (); |
10 |
11 |
} |
12 |
13 |
}; |
需要注意的是,在索引中使用游标时,由于可能有多个键值是相同的,这时候总是返回最低边界的那一个对象,为解决此问题,将NEXT_NO_DUPLICATE 或是PREV_NO_DUPLICATE做为它的第二个参数,如下:
1 |
index.openKeyCursor( null , IDBCursor.NEXT_NO_DUPLICATE).onsuccess = function (event) { |
2 |
var cursor = event.target.result; |
3 |
if (cursor) { |
4 |
// Do something with the entries. |
5 |
cursor. continue (); |
6 |
} |
7 |
}; |
十、 数据库版本的变化
当web app需要请求数据库的变化时,要考虑用户在一个tab中打开老版本的app,而在另一个tab窗口中打开新版本的app时会发生什么情况,当你调用setVersion()时,所有其它打的数据库必须显示的接受该请求时,你才能对数据库进行更改。
01 |
mozIndexedDB.open( "MyTestDatabase" ).onsuccess = function (event) { |
02 |
03 |
var db = event.target.result; |
04 |
05 |
// If the database is at the correct version then we can skip straight to using it. |
06 |
07 |
if (db.version == "1.0" ) { |
08 |
09 |
useDatabase(db); |
10 |
11 |
return ; |
12 |
13 |
} |
14 |
15 |
// Check that the database isn't a newer version already. |
16 |
17 |
if (db.version != "" ) { |
18 |
19 |
alert( "Database has a version which we don't know how to upgrade!" ); |
20 |
21 |
return ; |
22 |
23 |
} |
24 |
25 |
// Otherwise we need to change the version. |
26 |
27 |
var request = db.setVersion( "1.0" ); |
28 |
29 |
request.onblocked = function (event) { |
30 |
31 |
// If some other tab is loaded with the database, then it needs to be closed |
32 |
33 |
// before we can proceed. |
34 |
35 |
alert( "Please close all other tabs with this site open!" ); |
36 |
37 |
}; |
38 |
39 |
request.onsuccess = function (event) { |
40 |
41 |
// All other databases have been closed. Set everything up. |
42 |
43 |
db.createObjectStore( /* ... */ ); |
44 |
45 |
useDatabase(db); |
46 |
47 |
}; |
48 |
49 |
}; |
50 |
51 |
function useDatabase(db) { |
52 |
53 |
// Make sure to add a handler to be notified if another page requests a version |
54 |
55 |
// change. We must close the database. This allows the other page to upgrade the database. |
56 |
57 |
// If you don't do this then the upgrade won't happen until the user close the tab. |
58 |
59 |
db.onversionchange = function (event) { |
60 |
61 |
db.close(); |
62 |
63 |
alert( "A new version of this page is ready. Please reload!" ); |
64 |
65 |
}; |
66 |
67 |
// Do stuff with the database. |
68 |
69 |
} |
十一、 使用javascript Generators
注意:这只能在firefox中使用,不支持IE、chrome、Safari
Generators 在firefox中用于简化异步代码,但只能在javascript 1.7及后续的版本上,如:
<script type="text/javascript;version=1.7" src="myScript.js"></script>
myScript.js的内容如下:
01 |
// Need to stash the generator in a global variable. |
02 |
03 |
var generator; |
04 |
05 |
// Simple event listener function to pass the received event to the generator. |
06 |
07 |
function grabEvent(event) { |
08 |
09 |
generator.send(event); |
10 |
11 |
} |
12 |
13 |
// When we're all done we can close the generator, but that must happen outside |
14 |
15 |
// of the generator so we use a timeout. |
16 |
17 |
function closeGenerator() { |
18 |
19 |
setTimeout( function () { |
20 |
21 |
generator.close(); |
22 |
23 |
}, 0); |
24 |
25 |
} |
26 |
27 |
// Our main steps |
28 |
29 |
function databaseOperation() { |
30 |
31 |
mozIndexedDB.open( "MyTestDatabase" ).onsuccess = grabEvent; |
32 |
33 |
var event = yield; |
34 |
35 |
var db = event.target.result; |
36 |
37 |
if (db.version != "1.0" ) { |
38 |
39 |
db.setVersion( "1.0" ).onsuccess = grabEvent; |
40 |
41 |
event = yield; |
42 |
43 |
var transaction = event.transaction; |
44 |
45 |
db.createObjectStore( "stuff" ); |
46 |
47 |
transaction.oncomplete = grabEvent; |
48 |
49 |
yield; |
50 |
51 |
} |
52 |
53 |
db.transaction([ "stuff" ]).objectStore( "stuff" ).get( "foo" ).onsuccess = grabEvent; |
54 |
55 |
event = yield; |
56 |
57 |
alert( "Got result: " + event.target.result); |
58 |
59 |
// We're all done. |
60 |
61 |
closeGenerator(); |
62 |
63 |
// Always have an extra yield at the end or you will see StopIteration |
64 |
65 |
// exceptions. |
66 |
67 |
yield; |
68 |
69 |
} |
70 |
71 |
generator = databaseOperation(); |
72 |
73 |
generator.next(); |
十二、 安全性
需要注意的是IndexedDB在以iframe等方式加载到其它网站/网页是是不可运用的。
IDBRequest
它反应了向数据库IDBDatabase发出的请求的状况
属性:onsuccess --- 类型为函数,请求成功后执行,参数为请求成功产生的event(IDBSuccessEvent, IDBTransactionEvent)
Onerror --- 类型为函数,请求出错时执行,参数为错误时的event(IDBErrorEvent)
readyState --- 请求的状态,“1”为正在执行,“2”为执行完成
示例:
1 |
<pre> |
2 |
3 |
var request = mozIndexedDB.open( "MyTestDatabase" ) ; |
4 |
5 |
request.onerror = function (event) { //handle error }; |
6 |
7 |
request.onsuccess= function (event) { var db = request.result; //得到数据库对象,或db=event.target.result; }; |
8 |
9 |
</pre> |
IDBSuccessEvent向IndexedDB请求成功后产生的DOM事件属性:使用event.target.result来得到请求成功后返回的结果,参见IDBRequest中示例!