且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

IndexedDB简介与入门

更新时间:2022-10-04 17:50:07


  

背景

在本地离线存储的场景中,IndexedDB作为一个nosql的本地DB存储一直发挥着重要的作用。在需要存储大量数据时,IndexedDB能够有效地满足你的需求。下面,我将简单介绍下IndexedDB的简单使用方法和在我阅读W3C规范文档时看到的一些需要注意的细节。实例大部分参照MDN中的介绍实例,同时对整体逻辑和实例做了一些改动。

使用方法

打开数据库

我们使用window.indexedDB.open(DBName)来打开数据库。具体实例如下:


  1. const request = window.indexedDB.open(DBName); 
  2. request.onsuccess = function(event) { 
  3.     //request === event.target; 
  4. request.onerror = function(event) {}  

当数据库连接时,会返回一个IDBOpenDBRequest对象。在连接建立成功时,会触发onsuccess事件,其中函数参数event的target属性就是request对象。

创建和更新数据库版本号

window.indexedDB.open的第二个参数即为版本号。在不指定的情况下,默认版本号为1。具体实例如下:


  1. const request = window.indexedDB.open(DBName, 2); 

在需要更新数据库的schema(模式)时,需要更新版本号。此时我们指定一个高于之前版本的版本号,就会触发onupgradeneeded事件。类似的,在第一次创建时,也会触发此事件。

我们需要注意的是,版本号是一个unsigned long long数字,这意味着它可以是一个非常大的整数。但是,它不能是一个小数,否则它将会被转为最近的整数,同时有可能导致onUpgradeneeded事件不触发(bug)。

构建数据库

我们使用createObjectStore来创建一个存储空间。同时,使用createIndex来创建索引。具体实例如下:


  1. const objectStore = db.createObjectStore('customers'); 
  2.  
  3. objectStore.createIndex('name''name', {unique:false});  

我们从第一个函数createObjectStore开始说起。该函数接受两个参数,第一个参数为存储空间的名称,即我们上面的customers。同时,它还有第二个可选参数,keyPath指定存储的key值为存储对象的某个属性,autoIncrement指定了key值是否自增(当key值为默认的从1开始到2^53的整数时)。具体实例如下:


  1. const objectStore = db.createObjectStore('customers',{keyPath:'id', autoIncrement:true}); 

第二个函数为createIndex,它的第一个参数为索引的名称,第二个参数是指定根据存储数据的哪一个属性来构建索引,第三个属性unique为是否允许指定的索引值是否可以相等。具体示例如下:


  1. objectStore.createIndex('by_name''name', {unique:false}); 
  2.  
  3. objectStore.createIndex('by_email''email', {unique:true});  

当存储空间初始化完成后,我们需要把数据放入存储空间中,我们可以直接调用add方法将数据放入存储空间内,具体实例如下:


  1. //方法1,规范文档推荐使用, key值如果指定自增,可以不填 
  2. objectStore.put(data, key); 
  3. //方法2,key值同上 
  4. objectStore.add(data, key);  

事务

在IndexedDB中,我们也能够使用事务来进行数据库的操作。事务有三个模式:

  • readOnly,只读
  • readwrite,读写
  • versionchange,数据库版本变化

我们创建一个事务时,需要从上面选择一种模式,如果不指定的话,则默认为只读模式。具体实例如下:


  1. const transition = db.transition(['customers'], 'readwrite'); 

事务函数transition的第一个参数为需要关联的存储空间,第二个可选参数为事务模式。与上面类似,事务成功时也会触发onsuccess函数,失败时触发onerror函数。

事务的操作都是原子性的。

删除

使用delete函数即可删除数据。具体使用方法如下:


  1. const request = db.transaction(['customers'], 'readwrite').objectStore('customers').delete(author); 

获取数据

get方法

最简单的方法就是使用自带的get函数来获取存储空间的值,具体实例如下:


  1. db.transition(['customer']).objectStore('customer').get(keyName).onuccess = function(){}; 

游标方法

我们使用openCursor来创建游标。


  1. const objectStore = db.transaction('customers').objectStore('customers'); 
  2.  
  3. objectStore.openCursor().onsuccess = function(event) { 
  4.   const cursor = event.target.result; 
  5.   if (cursor) { 
  6.     alert(cursor.value.name); 
  7.     cursor.continue(); 
  8.   } 
  9.   else { 
  10.     alert('No more entries!'); 
  11.   } 
  12. }; 

使用游标时有一个需要注意的地方,当游标便利整个存储空间但是并未找到给定条件的值时,仍然会触发onsuccess函数。

同时,如果你想要限定你在游标中看到的值的范围,你可以使用一个key range对象然后把它作为第一个参数传给openCursor或是openKeyCursor方法。你可以构造一个只允许一个单一key的 key range,或者一个具有下限或上限,或者一个既有上限也有下限。边界可以是闭合的(也就是说key range包含给定的值)或者是“开放的”(也就是说key range不包括给定的值)。具体实例如下:


  1. // 只匹配 'Donna' 
  2. const singleKeyRange = IDBKeyRange.only('Donna'); 
  3.  
  4. // 匹配所有在 'Bill' 前面的, 包括 'Bill' 
  5. const lowerBoundKeyRange = IDBKeyRange.lowerBound('Bill'); 
  6.  
  7. // 匹配所有在 “Bill” 前面的, 但是不需要包括 'Bill' 
  8. const lowerBoundOpenKeyRange = IDBKeyRange.lowerBound('Bill'true); 
  9.  
  10. // Match anything up to, but not including, 'Donna' 
  11. const upperBoundOpenKeyRange = IDBKeyRange.upperBound('Donna'true); 
  12.  
  13. //Match anything between 'Bill' and 'Donna', but not including 'Donna' 
  14. const boundKeyRange = IDBKeyRange.bound('Bill''Donna'falsetrue); 
  15.  
  16. index.openCursor(boundKeyRange).onsuccess = function(event) { 
  17.   const cursor = event.target.result; 
  18.   if (cursor) { 
  19.     // Do something with the matches. 
  20.     cursor.continue(); 
  21.   } 
  22. };  

IDBKeyRange对象的lowerBound和upperBound方法分别表示检索指定key值前或者后的范围(第二个参数指定边界是否闭合,如果为true则不闭合)。

有时候你可能想要以倒序而不是正序(所有游标的默认顺序)来遍历。切换方向是通过传递prev到 openCursor方法来实现的。


  1. objectStore.openCursor(null, IDBCursor.prev).onsuccess = function(event) { 
  2.   const cursor = event.target.result; 
  3.   if (cursor) { 
  4.     // Do something with the entries. 
  5.     cursor.continue(); 
  6.   } 
  7. };  

因为 “name” 索引不是唯一的,那就有可能存在具有相同 name 的多条记录。要注意的是这种情况不可能发生在对象存储空间上,因为键必须永远是唯一的。如果你想要在游标在索引迭代过程中过滤出重复的,你可以传递 nextunique (或 prevunique 如果你正在向后寻找)作为方向参数。 当 nextunique 或是 prevunique 被使用时,被返回的那个总是键最小的记录。

索引方法

在前面构建数据库时,我们创建了两个索引。现在,我们就通过这两个索引来演示如何通过索引进行搜索。具体实例如下:


  1. const index = objectStore.index('by_name'); 
  2. //第一种,使用get方法 
  3. index.get(name).onsuccess = function(){ 
  4.     alert(event.target.result.name); 
  5. //第二种,使用游标 
  6. //普通游标 
  7. index.openCursor().onsuccess = function(event) { 
  8.   const cursor = event.target.result; 
  9.   if (cursor) { 
  10.     // cursor.key 是一个 name, 就像 'Bill', 然后 cursor.value 是整个对象。 
  11.     alert('Name: ' + cursor.key + ', id' + cursor.value.id + ', email: ' + cursor.value.email); 
  12.     cursor.continue(); 
  13.   } 
  14. }; 
  15.  
  16. //键游标 
  17. index.openKeyCursor().onsuccess = function(event) { 
  18.   const cursor = event.target.result; 
  19.   if (cursor) { 
  20.     // cursor.key is 一个 name, 就像 'Bill', 然后 cursor.value 是那个id,即存储对象的key值。 
  21.     alert('Name: ' + cursor.key + ', 'id: ' + cursor.value); 
  22.     cursor.continue(); 
  23.   } 
  24. };  

异常处理

在浏览器有如下操作的情况下,indexedDB可能会出现异常:

  • 用户清除浏览器缓存
  • 存储空间超过大小限制

W3C规范文档需要注意的应用点

key值能够接受的数据类型

在IndexedDB中,key值可以接受一下几种类型的值:

  • Number
  • Date
  • String
  • ArrayBuffer
  • Array

具体说明可以参考此处。

key path能够接受的数据类型

在IndexedDB中,key path(主键)能够接接受如下数据类型:

  • Blob
  • File
  • Array
  • StringI

注:空格不能出现在key path中。

value能够接受的数据类型

在IndexedDB中,value能够接受ECMA-262中所有的值,例如String,Date,ImageDate等。

事务中断后,会不会影响key值的自增

IndexedDB在没有指定key值的时候就会采用自增的key值,不受其他任何影响。如果一个事务在中途中断,那么key值的自增将会从事务开始前的key开始。

IndexedDB的安全相关问题

IndexedDB也受到浏览器同源策略的限制。

总结

IndexedDB在本地存储中有着无可替代的作用,是替代关系型数据库websql的产品。在许多需要运用离线存储的场景下,IndexedDB能够给我们提供有效的支撑。但是,IndexedDB毕竟存在于客户端,会出现诸如用户清理缓存(Windows下更为常见)等破坏数据的情况,因此建议作为一个优化用户体验的方案,不能过多依赖。

作者:hjava
来源:51CTO