什么是indexedDB indexedDB是浏览器结构化存储的一种方式,用于代替目前已经废弃的Web SQL Database API
indexedDB是类似于MySQL或者Web SQL Database的数据库,但是**IndexDB使用对象而不是表格保存数据,因此,可以说indexedDB是在一个公共空间下的一组对象存储 ,类似于NoSQL风格的实现
如何使用IndexDB数据库 使用indexedDB数据库可通过如下步骤(基础):
Step1:调用indexedDB.open()方法 
Step2:对象存储 
Step3:调用事务进行增删改查 
 
Step1:调用indexedDB.open()方法 indexedDB.open()方法用于打开数据库(如果数据库不存在,则为创建)
参数说明 
返回值说明 
返回一个IDBRequest的实例,可以在这个实例上添加onerror和onsuccess事件处理程序,这两个事件处理程序的event.target都指向这个实例 
如果打开的时候发生错误,就会在event.target.errorCode中存储错误码 
如果打开成功,就可以通过event.target.result访问数据库 
 
1 2 3 4 5 6 7 8 9 10 11 let  db,    request,     version = 1 ; request = indexedDB.open ('admin' , version);    request.onerror  = (event ) =>  {     console .log (`Failed to open: ${event.target.errorCode} ` ); }; request.onsuccess  = (event ) =>  {     db = event.target .result ; }; 
 
Step2:对象存储 对象存储你需要准备:
1 2 3 4 5 6 let  user = {    username : '007' ,     firstName : 'Katrina' ,     lastName : 'Ying' ,     password : 'foo' , } 
 
假设要存储这样一个对象,很显然username可以用来做主键
1 2 3 4 5 6 7 8 9 10 request.onupgradeneeded  = (event ) =>  {     const  db = event.target .result ;               if  (db.objectStoreNames .contains ('users' )) {         db.deleteObjectStore ('users' );     }          db.createObjectStore ('users' , {keyPath : 'username' }); } 
 
Step3:通过事务进行后序操作 在对象存储之后,剩下的所有操作都是通过事务完成的
插入对象:add put 
删除对象:delete (键作为参数) 
获取对象:get(键作为参数) 
清空对象:clear 
 
1. 创建事务 db.transaction() 
没有指定名称——数据库中所有的对象都只有只读权限 
访问一个或者多个数据库——传入数据库名称,单个直接传入,多个以数组的形式传入 
传入第二个参数以修改访问模式——readonly readwrite versionchange 
 
1 2 3 4 5 6 7 8 9 let  transaction = db.transaction ();  let  transaction = db.transaction ('users' );  let  transaction = db.transaction (['users' , 'admin' ]);  let  transaction = db.transaction ('users' , 'readwrite' );  
 
2. 事务事件处理程序绑定 1 2 3 4 5 6 const  transaction = db.transaction ('users' ),      store = transaction.objectStore ('users' ),       request = store.get ('007' ); request.onerror  = (event ) =>  alert ("Did not get the object!" ); request.onsuccess  = (event ) =>  alert (event.target .result .firstName ); 
 
因为一个事务可以完成任意多个请求,所以事务对象本身也有事件处理程序:onerror 和oncomplete,这两个事件可以用来获取事务级的状态信息
1 2 3 4 5 6 transaction.onerror  = (event ) =>  { 	 }; transaction.oncomplete  = (event ) =>  { 	 }; 
 
注意,不能通过oncomplete事件处理程序的event对象访问get()请求返回的任何数据,因此,仍然需要通过这些请求的onsuccess事件处理程序来获取数据
插入数据 插入数据的方式有:
这两个方法都接收一个参数:要存储的对象
两者的区别在于:传入对象存储已经包含同名键时,add会报错,而put是重写对象
1 2 3 for  (let  user of  users) {    store.add (user); } 
 
每次调用add()或put()都会创建对象存储的新更新请求
如果想验证请求成功与否,可以把请求对象保存到一个变量,然后为它添加onerror 和onsuccess 事件处理程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let  request,    requests = []; for  (let  user of  users) {    request = store.add (user);          request.onerror  = () =>  {              };          request.onsuccess  = () =>  {              };          requests.push (request); } 
 
通过游标查询 使用事务可以通过一个已知键取得一条记录,如果想取得多条数据,则需要在事务中创建一个游标 
游标是一个指向结果集的指针
与传统数据库查询不同,游标不会事先收集所有结果,相反,游标指向第一个结果,并在接到指令前不会主动查找下一条数据
创建游标 openCursor() 可以接收两个参数:(后面讲!!!)
openCursor()也返回一个请求,所以必须为它添加onsuccess和onerror事件处理程序
1 2 3 4 5 6 7 8 9 10 11 const  transaction = db.transaction ('users' ),      store = transaction.objectStore ('users' ),       request = store.openCursor (); request.onerror  = () =>  {      };      request.onsuccess  = () =>  {      }; 
 
在调用onsuccess事件处理程序时,可以通过event.target.result访问对象存储中的下一条记录,这个属性中保存着IDBCursor的实例(有下一条记录时)或null(没有记录时)
实例有如下属性:
direction:字符串常量,表示游标的前进方向以及是否应该遍历所有重复的值。
可能的值包括:NEXT(“next”)、NEXTUNIQUE(“nextunique”)、PREV(“prev”)、PREVUNIQUE(“prevunique”) 
 
 
key:对象的键 
value:实际的对象 
primaryKey:游标使用的键,可能是对象键或索引键 
 
1 2 3 4 5 6 request.onsuccess  = (event ) =>  {     const  cursor = event.target .result ;     if  (cursor) {           console .log (`Key:${cursor.key} ,Value:${JSON .stringify(cursor.value)} ` )     } } 
 
 
游标可用于更新个别记录——update()
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 request.onsuccess  = (event ) =>  { 	const  cursor = event.target .result ; 	let  value, 		updateRequest; 	if  (cursor) {  		if  (cursor.key  == "foo" ) { 			value = cursor.value ;  			value.password  = "magic!" ;  			updateRequest = cursor.update (value);  			updateRequest.onsuccess  = () =>  { 				 			}; 			updateRequest.onerror  = () =>  { 				 			}; 	} 	} }; 
 
可以删除游标位置的记录_delete() 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 request.onsuccess  = (event ) =>  { 	const  cursor = event.target .result ; 	let  value, 		deleteRequest; 	if  (cursor) {  		if  (cursor.key  == "foo" ) { 			deleteRequest = cursor.delete ();  			deleteRequest.onsuccess  = () =>  { 				 			}; 			deleteRequest.onerror  = () =>  { 				 			}; 		} 	} }; 
 
注意:如果事务没有修改对象存储的权限,那么update和delete都会报错
游标迭代 默认情况下,每个游标只会创建一个请求
要创建另一个请求,必须调用下列中的一个方法
continue(key):移动到结果集中的下一条记录,参数key 是可选的,如果没有指定key,游标就移动到下一条记录;如果指定了,则游标移动到指定的键 
advance(count):游标向前移动指定的count 条记录,这两个方法都会让游标重用相同的请求,因此也会重用onsuccess 和onerror 处理程序,直至不再需要 
 
1 2 3 4 5 6 7 8 9 request.onsuccess  = (event ) =>  { 	const  cursor = event.target .result ; 	if  (cursor) {  		console .log (`Key: ${cursor.key} , Value: ${JSON .stringify(cursor.value)} ` ); 		cursor.continue ();  	} else  { 		console .log ("Done!" ); 	} }; 
 
键范围 可以使用键范围更容易地管理游标
键范围对应IDBKeyRange的实例
有四种方式指定键范围
1 const  onlyRange = IDBKeyRange .only ('007' );
 
这个范围保证只获取键为”007”的值
使用这个范围创建的游标类似于直接访问对象存储并调用get(“007”)
定义结果集的下限,下限表示游标开始的位置
 
1 const  lowerRange = IDBKeyRange .lowerRange ('007' );   
 
定义结果集的上限,通过调用upperBound()方法可以指定游标不会越过的记录,如果不想包含指定的键,可以在第二个参数传入true
 
1 2 3 4 5 const  upperRange = IDBKeyRange .upperBound ("ace" );const  upperRange = IDBKeyRange .upperBound ("ace" , true );
 
同时指定下限和上限,这个方法接收四个参数:下限的键、上限的键、可选的布尔值表示是否跳过下限和可选的布尔值表示是否跳过上限
 
1 2 3 4 5 6 7 8 const  boundRange = IDBKeyRange .bound ("007" , "ace" );const  boundRange = IDBKeyRange .bound ("007" , "ace" , true );const  boundRange = IDBKeyRange .bound ("007" , "ace" , true , true );const  boundRange = IDBKeyRange .bound ("007" , "ace" , false , true );
 
游标范围设置——传入openCursor 1 2 3 4 5 6 7 8 9 10 11 12 13 const  store = db.transaction ("users" ).objectStore ("users" ),	range = IDBKeyRange .bound ("007" , "ace" ); request = store.openCursor (range); request.onsuccess  = function (event ){ 	const  cursor = event.target .result ; 	if  (cursor) {  		console .log (`Key: ${cursor.key} , Value: ${JSON .stringify(cursor.value)} ` ); 		cursor.continue ();  	} else  { 		console .log ("Done!" ); 	} }; 
 
设置游标方向 游标方向:
next(默认):从第一条到最后一条,不跳过重复项 
nextunique:从第一条到最后一条,跳过重复项 
prev:从最后一条到第一条,不跳过重复项 
prevunique:从最后一条到第一条,跳过重复项 
 
索引 创建新索引 createIndex() 参数说明 
索引名称 
索引属性名称 
包含键unique的options对象
unique必须指定,表示这个键是否在所在记录里面唯一 
 
 
 
1 2 3 const  transaction = db.transaction ("users" ),	store = transaction.objectStore ("users" ), 	index = store.createIndex ("username" , "username" , { unique : true  }); 
 
返回值说明 
返回IDBIndex实例
1 2 3 const  transaction = db.transaction ("users" ),	store = transaction.objectStore ("users" ), 	index = store.index ("username" );   
 
可以在索引上使用openCursor()方法创建新游标,这个游标与在对象存储上调用openCursor()创建的游标完全一样,只是其result.key 属性中保存的是索引键,而不是主键
1 2 3 4 5 6 7 8 const  transaction = db.transaction ("users" ),	store = transaction.objectStore ("users" ), 	index = store.index ("username" ), 	request = index.openCursor (); request.onsuccess  = (event ) =>  { 	 }; 
 
 
使用openKeyCursor()方法也可以在索引上创建特殊游标,只返回每条记录的主键
这个方法接收的参数与openCursor()一样
最大的不同在于,event.result.key 是索引键,且event.result.value是主键而不是整个记录 
1 2 3 4 5 6 7 8 9 const  transaction = db.transaction ("users" ),	store = transaction.objectStore ("users" ), 	index = store.index ("username" ), 	request = index.openKeyCursor (); request.onsuccess  = (event ) =>  { 	 	 }; 
 
 
如果想只取得给定索引键的主键,可以使用getKey()方法,这样也会创建一个新请求,但result.value 等于主键而不是整个记录
1 2 3 4 5 6 7 8 9 const  transaction = db.transaction ("users" ),	store = transaction.objectStore ("users" ), 	index = store.index ("username" ), 	request = index.getKey ("007" ); request.onsuccess  = (event ) =>  { 	 	 }; 
 
在这个onsuccess 事件处理程序中,event.target.result.value 中应该是用户ID
任何时候,都可以使用IDBIndex 对象的下列属性取得索引的相关信息
name:索引的名称 
keyPath:调用createIndex()时传入的属性路径 
objectStore:索引对应的对象存储 
unique:表示索引键是否唯一的布尔值 
 
对象存储自身也有一个indexNames 属性,保存着与之相关索引的名称
使用如下代码可以方便地了解对象存储上已存在哪些索引
1 2 3 4 5 6 7 8 9 10 const  transaction = db.transaction ("users" ),	store = transaction.objectStore ("users" ), 	indexNames = store.indexNames  for  (let  indexName in  indexNames) {	const  index = store.index (indexName); 	console .log (`Index name: ${index.name}   		KeyPath: ${index.keyPath}  		Unique: ${index.unique} ` );} 
 
 
在对象存储上调用deleteIndex()方法并传入索引的名称可以删除索引
1 2 3 const  transaction = db.transaction ("users" ),	store = transaction.objectStore ("users" ), 	store.deleteIndex ("username" ); 
 
因为删除索引不会影响对象存储中的数据,所以这个操作没有回调
 
 
并发问题 IndexedDB虽然是网页中的异步API,但仍存在并发问题
如果两个不同的浏览器标签页同时打开了同一个网页,则有可能出现一个网页尝试升级数据库而另一个尚未就绪的情形
有问题的操作是设置数据库为新版本,而版本变化只能在浏览器只有一个标签页使用数据库时才能完成
第一次打开数据库时,添加onversionchange 事件处理程序非常重要,另一个同源标签页将数据库打开到新版本时,将执行此回调,对这个事件最好的回应是立即关闭数据库,以便完成版本升级
1 2 3 4 5 6 let  request, database;request = indexedDB.open ("admin" , 1 ); request.onsuccess  = (event ) =>  { 	database = event.target .result ; 	database.onversionchange  = () =>  database.close (); }; 
 
应该在每次成功打开数据库后都指定onversionchange事件处理程序
记住,onversionchange有可能会被其他标签页触发
通过始终都指定这些事件处理程序,可以保证Web应用程序能够更好地处理与IndexedDB相关的并发问题
限制 
IndexedDB 数据库是与页面源(协议、域和端口)绑定的,因此信息不能跨域共享。这意味着www.wrox.com  和p2p.wrox.com 会对应不同的数据存储 
每个源都有可以存储的空间限制。当前Firefox 的限制是每个源50MB,而Chrome 是5MB,移动版Firefox 有5MB 限制,如果用度超出配额则会请求用户许可 
Firefox 还有一个限制——本地文本不能访问IndexedDB 数据库,Chrome 没有这个限制。