JavaScript

JavaScript 知识量:26 - 101 - 483

24.3 IndexedDB><

IndexedDB简介- 24.3.1 -

IndexedDB是一种在浏览器端存储大量结构化数据的底层API。它使用索引实现对数据的高性能搜索,可以存储和检索大量的数据,包括文件/二进制大型对象(blobs)。与Web Storage相比,IndexedDB适用于存储更大量的结构化数据。

IndexedDB是一种key-value型的数据库,value可以是复杂的结构体对象,而key可以是对象的某些属性值或其他对象(包括二进制对象)。可以使用对象中的任何属性作为index,以加快查找。

IndexedDB自带transaction,所有的数据库操作都会绑定到特定的事务上,并且这些事务是自动提交的。IndexedDB并不支持手动提交事务。大部分的IndexedDB API都是异步的,异步API的本质是向数据库发送一个操作请求,当操作完成的时候,会收到一个DOM event,通过该event,可以知道操作是否成功,并且获得操作的结果。

IndexedDB是一种NoSQL数据库,它存储的是Javascript对象。此外,IndexedDB还具有同源策略,每个源都会关联到不同的数据库集合,不同源是不允许访问其他源的数据库,从而保证了IndexedDB的安全性。

使用IndexedDB- 24.3.2 -

使用IndexedDB需要以下步骤:

  1. 打开数据库连接:使用indexedDB.open()方法打开数据库连接,该方法返回一个IDBRequest对象,可以通过该对象进行后续操作。

  2. 创建数据库:在打开数据库连接后,需要创建一个新的数据库,可以使用IDBDatabase对象来实现。可以通过传递一个数据库名称和版本号来创建数据库。

  3. 创建对象存储空间:在创建数据库后,需要创建一个或多个对象存储空间,可以使用IDBObjectStore对象来实现。可以通过传递一个存储空间名称、一个key path和是否允许重复键来确定存储空间的属性。

  4. 添加数据:可以使用IDBObjectStore.put()方法将数据添加到对象存储空间中。可以将一个或多个键值对作为参数传递给该方法。如果添加的数据已经存在,则会更新该数据。

  5. 获取数据:可以使用IDBObjectStore.get()方法从对象存储空间中获取数据。可以将一个键作为参数传递给该方法,并返回对应的值。

  6. 删除数据:可以使用IDBObjectStore.delete()方法从对象存储空间中删除数据。可以将一个键作为参数传递给该方法。

  7. 关闭数据库连接:在完成所有操作后,需要关闭数据库连接,可以使用IDBDatabase.close()方法来实现。

需要注意的是,IndexedDB是一个异步API,所有的操作都是异步进行的,需要使用回调函数或Promise来处理异步操作的结果。此外,IndexedDB的操作都是事务性的,每个操作都会绑定到一个事务中,事务的提交是自动进行的。

对象存储- 24.3.3 -

IndexedDB内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括JavaScript对象。在IndexedDB中,数据以"键值对"的形式保存在对象仓库中,每一个数据记录都有一个对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。

使用IndexedDB时,可以通过一个key作为索引进行存储或者获取数据。事务是IndexedDB中非常重要的概念,可以在事务中完成对数据的修改。IndexedDB支持事务,并且事务是自动提交的。

此外,IndexedDB还具有同源限制,每个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。IndexedDB的储存空间比LocalStorage大得多,一般来说不少于250MB,甚至没有上限。它不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer对象和Blob对象)。

事务- 24.3.4 -

IndexedDB是一个事务型数据库,它支持事务(transaction)。事务是操作数据库的一种方式,它可以将多个操作组合在一起,要么全部执行,要么全部不执行。在执行事务的过程中,如果有一步失败,整个事务就会被取消,数据库会进行回滚,回到事务发生前的状态。

在IndexedDB中,事务由IDBDatabase对象调用其transaction方法创建。该方法接受两个参数:storeNames和mode。storeNames参数是一个字符串数组,用于指明用户希望访问的objectStore。如果仅想访问一个objectStore,那么只需传入一个字符串而没必要传一个数组。为了避免性能下降,如果不是要对数据库写入数据就不要用readwrite模式打开事务。

在IndexedDB的事务中,如果存在一个不成功的事务,db属性的值会返回一个DOMException来指示错误对象。当事务尚未完成、完成了且成功提交或者是手动调用abort方法时,这个属性的值都为null。

IndexedDB的事务能确保数据的一致性和完整性,它通过原子性操作确保了在对数据库进行一系列操作时,如果没有错误发生,所有的操作都会被执行。如果发生错误,那么所有的操作都不会被执行,数据库会回滚到事务开始之前的状态。

插入对象- 24.3.5 -

IndexedDB 可以通过 add() 或 put() 方法将数据写入数据库。

  • add()方法:此方法创建一个新的数据库条目。如果已经存在具有相同键的条目,则此操作将失败并抛出异常。

  • put()方法:此方法更新现有的数据库条目。如果数据库中不存在具有相同键的条目,则此操作将创建一个新条目。

这两个方法都接受一个键/值对作为参数,其中键是用于唯一标识存储在数据库中的数据的主键,而值是要存储的实际数据。

例如,假设有一个名为“users”的对象存储空间,其中包含用户的姓名和年龄,可以使用以下代码将新用户添加到数据库中:

var request = indexedDB.open("myDatabase", 1);  
  
request.onerror = function(event) {  
  // Handle errors  
};  
  
request.onsuccess = function(event) {  
  var db = request.result;  
    
  db.onerror = function(event) {  
    // Handle database errors  
  };  
    
  var transaction = db.transaction(["users"], "readwrite");  
  var store = transaction.objectStore("users");  
    
  var newUser = { "name": "John", "age": 30 };  
    
  store.put(newUser, "newUserKey");  
};

在这个例子中,首先打开一个名为“myDatabase”的数据库,并在打开成功后获取数据库对象。然后,创建一个包含用户名和年龄的对象,并使用 put() 方法将其添加到名为“users”的对象存储空间中,使用 "newUserKey" 作为键。

通过游标查询- 24.3.6 -

游标(Cursor)是用于在IndexedDB中查询数据的对象。游标允许按顺序遍历存储在数据库中的数据。以下是使用游标进行查询的基本步骤:

  1. 打开数据库连接并获取数据库对象。

  2. 创建一个事务对象,并传入想要操作的对象存储空间名称以及事务模式(读或读写)。

  3. 获取对象存储空间对象。

  4. 创建一个游标对象,通过调用openCursor()方法或者openKeyCursor()方法(如果只需要遍历键)并传入合适的范围和排序规则。

  5. 遍历游标对象,获取每个条目的数据和键。

  6. 在遍历过程中,可以通过advance()方法来控制遍历的步长。

  7. 关闭游标和事务,并关闭数据库连接。

以下是一个使用游标进行查询的示例代码:

// 打开数据库连接  
var request = indexedDB.open("myDatabase", 1);  
  
// 处理错误  
request.onerror = function(event) {  
  // Handle errors  
};  
  
// 处理成功打开数据库  
request.onsuccess = function(event) {  
  var db = request.result;  
    
  // 创建一个名为“users”的对象存储空间  
  var transaction = db.transaction(["users"], "readonly");  
  var store = transaction.objectStore("users");  
    
  // 创建一个游标对象,获取所有用户数据并按年龄升序排序  
  var cursorRequest = store.openCursor(null, "prev");  
    
  // 处理游标事件  
  cursorRequest.onerror = function(event) {  
    // Handle cursor error  
  };  
  cursorRequest.onsuccess = function(event) {  
    var cursor = cursorRequest.result;  
    if (cursor) {  
      // 获取当前条目的数据和键  
      var key = cursor.key;  
      var value = cursor.value;  
      console.log("Key: " + key + ", Value: " + value);  
        
      // 继续遍历下一个条目  
      cursor.continue();  
    } else {  
      // 遍历完成  
    }  
  };  
};

键范围- 24.3.7 -

使用游标会给人一种不太理想的感觉,因为获取数据的方式受到了限制。使用键范围(key range)可以让游标更容易管理。键范围对应IDBKeyRange的实例。

IDBKeyRange是一个非常实用的工具,它可以通过定义一个键的范围来查询数据库。通过使用IDBKeyRange,可以查询特定范围内的所有数据,或者在特定键上执行更复杂的查询。

IDBKeyRange有四个静态方法:

  • IDBKeyRange.lowerBound(lower): 这个方法返回一个新的IDBKeyRange实例,该实例表示一个键值大于或等于lower的范围。

  • IDBKeyRange.upperBound(upper): 这个方法返回一个新的IDBKeyRange实例,该实例表示一个键值小于或等于upper的范围。

  • IDBKeyRange.bound(lower, upper): 这个方法返回一个新的IDBKeyRange实例,该实例表示一个键值在lower和upper之间的范围。

  • IDBKeyRange.only(value): 这个方法返回一个新的IDBKeyRange实例,该实例表示一个键值等于value的范围。

这些方法都很有用,特别是想要在数据库中查询特定范围的数据时。例如,可能想要获取所有在特定日期之后添加的条目,或者获取所有在特定日期范围内添加的条目。使用IDBKeyRange可以更精确地控制查询,从而获取到需要的数据。

设置游标方向- 24.3.8 -

openCursor() 方法是 IndexedDB API 的一部分,用于打开一个游标以在数据库中遍历数据。然而,至 2021 年为止,IndexedDB API 并没有直接设置游标方向的方法。游标默认从数据库的起始位置开始,并按照数据库的默认排序规则进行遍历。

可以通过在 openCursor() 方法中传递不同的 direction 参数来改变遍历的方向。然而,至 2021 年为止,IndexedDB 只支持按升序或降序遍历,所以 direction 参数只能设置为 'next'(按升序向后遍历)、'prev'(按降序向前遍历)或 'nextunique'(按升序向前遍历,跳过已经遍历过的键)。

这是一个使用 openCursor() 方法并设置遍历方向的例子:

var transaction = db.transaction(['myStore'], 'readonly');  
var store = transaction.objectStore('myStore');  
var keyRange = IDBKeyRange.bound('lower', 'upper'); // 设置键的范围  
var direction = 'next'; // 设置遍历方向  
  
var cursorRequest = store.openCursor(keyRange, direction);  
  
cursorRequest.onsuccess = function(event) {  
  var cursor = cursorRequest.result;  
  if (cursor) {  
    // 处理获取到的数据  
    console.log(cursor.value);  
    cursor.continue(); // 继续遍历  
  } else {  
    // 遍历完成  
  }  
}

创建索引- 24.3.9 -

createIndex() 方法在 IndexedDB 中用于创建一个新的索引。索引是在数据库中存储和检索数据的一种方式。通过索引,可以根据指定的列的值快速查找和访问数据。

以下是 createIndex() 方法的基本语法:

var request = indexedDB.createIndex(databaseName, objectStoreName, indexName, 
keyPath, optionalParameters);

简要说明如下:

  • databaseName:要创建索引的数据库的名称。

  • objectStoreName:要在其中创建索引的对象存储的名称。

  • indexName:新索引的名称。

  • keyPath:指定用作索引键的值或路径。这是将要索引的数据列的路径。

  • optionalParameters:可选参数,用于定义索引的其他属性。例如,可以指定是否唯一索引,是否在后台线程中创建索引等。

以下是一个简单的示例,展示如何使用 createIndex() 方法在名为 "users" 的对象存储中创建一个名为 "username" 的索引:

var openRequest = indexedDB.open("myDatabase", 1);  
openRequest.onerror = function(event) {  
  // Handle errors  
};  
openRequest.onsuccess = function(event) {  
  var db = openRequest.result;  
  var transaction = db.transaction("users", "readwrite");  
  var store = transaction.objectStore("users");  
  store.createIndex("username", "username");  
};

在上面的示例中,首先打开名为 "myDatabase" 的数据库,并在成功打开后获取了数据库对象。然后,创建一个名为 "username" 的索引,该索引在 "username" 列上。通过使用 createIndex() 方法,现在可以在数据库中根据用户名进行快速检索。

IndexedDB的限制- 24.3.10 -

IndexedDB 有一些限制和约束,包括以下几点:

  1. 同源限制:IndexedDB 受到同源限制,每个数据库对应创建它们的域名。网页只能访问自身名下的数据库,而不能访问跨域的数据库。

  2. 存储空间:虽然具体的存储空间大小因浏览器和设备而异,但 IndexedDB 的存储空间通常较大,可以达到几百兆甚至更多。然而,这也意味着使用 IndexedDB 存储大量数据可能会占用较多的内存和存储空间。

  3. 数据类型:IndexedDB 可以存储多种类型的数据,包括字符串、二进制数据、日期和时间戳等。然而,对于一些特定类型的数据,如 ArrayBuffer 对象或 Blob 对象,IndexedDB 只允许以二进制形式存储。

  4. 事务处理:IndexedDB 采用事务处理方式,对于每个操作都必须是原子性操作,这可能会导致在处理大量数据时性能下降。此外,事务处理也受到浏览器页面的生命周期和可用内存的限制。

  5. 并发访问:IndexedDB 不支持并发访问。如果多个事务同时尝试访问同一数据,将会导致数据竞争和可能的冲突。

  6. 查询能力:IndexedDB 的查询能力有限,主要依赖于索引和游标。虽然可以创建多键索引来提高查询性能,但相对于其他数据库系统(如 MySQL 或 PostgreSQL),其查询功能可能不够强大。

  7. 浏览器兼容性:虽然大多数现代浏览器都支持 IndexedDB,但不同浏览器之间的兼容性可能会有所差异。在使用 IndexedDB 时,需要考虑不同浏览器的兼容性问题。