什么是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 没有这个限制。