Indexed Database使用指南

前言

本篇文章是关于indexDB我的一些学习分享,学习内容来自indexDB W3C规范,大家可以直接查看规范学习,如果对英文规范有理解上的困难,亦可查阅此篇文章学习,这里对规范的内容做了提炼总结,并附上一些实践案例,如果通过阅读此文让你对indexDB的开发实践有比较透彻的理解,那真是一件令人快乐的事儿~

背景

在实际业务开发中,我们通常会有一些大量复杂的数据结构的请求和处理,这些数据如果通过接口获取会比较耗费带宽,影响性能,且一次性加载大量数据在内存中,会对浏览器造成较大的内存压力。这时候就需要一个比localStorage存储量更大的,存储结构更灵活的存储机制,Indexed Database就是其中一种。

它通常是通过使用持久性的B树的数据结构来实现的,这些数据结构被认为对于数据的插入、删除以及大量数据的有序遍历更高效。

1. 简介

入门🌰(在线地址):

const request = indexedDB.open('indexdb_init', 1);
let db;
request.onupgradeneeded = function(event) {
  // 多版本并行
  const db = request.result;
  if(event.oldVersion < 1) {
    const store = db.createObjectStore("books", { keyPath: 'isbn' });
    db.books = store;
    const titleIndex = store.createIndex("by_title", "title", { unique: true });
    const authorIndex = store.createIndex("by_author", "author");

  }
  if(event.oldVersion < 2) {
    const bookStore = request.transaction?.objectStore("books");
    const yearIndex = bookStore?.createIndex("by_year", "year");
  }
  if(event.oldVersion < 3) {
    const magazines = db.createObjectStore("magazines");
    const publisherIndex = magazines.createIndex("by_publisher", "publisher");

  }

}
request.onsuccess = function() {
  db = request.result;
  console.log(db, db.parent, 'd-------')
}

request.onerror = function(e) {
  console.log("open database error", e);
} 

多个客户端(pages 和 workers)可以同时使用一个数据库,transaction(事务)保证他们不会在读取和写入的时候不会发生冲突,如果一个客户端想要升级数据库(通过 upgradeneeded 事件),只有当所有其他客户端关闭他们与数据库当前版本的连接才可以。

为了避免阻止数据库升级,客户端可以监听versionchange事件,当其中一个页面或者线程想要升级数据库时触发,为了让这种情况继续下去,请通过执行最终关闭此页面(线程)与数据库连接的操作来响应versionchange

其中一种方式是重载页面。

db.onversionchange = function() {
  // 第一步, 保存数据:
  saveUnsavedData().then(function() {
    // 如果页面处于未激活状态,那么在没有用户交互的情况下重新加载页面是合适的
    if (!document.hasFocus()) {
      location.reload();
      // 重新加载会关闭数据库,并重新加载新的js和数据库定义
    } else {
      // 如果页面处于激活状态,重新加载页面可能会造成干扰,可能需要要求用户手动完成
      displayMessage("Please reload this page for the latest version.");
    }
  });};
  function saveUnsavedData() {
 
  }
  function displayMessage() {
  } 

另一种方法是调用连接的close方法,但是,你需要确保你的应用知道这一点,因为后续访问数据库的尝试都将失败。

db.onversionchange = function() {
  saveUnsavedData().then(function() {
    db.close();
    stopUsingTheDatabase();
  });
};
function stopUsingTheDatabase() {
} 

尝试升级的页面可以使用阻塞事件来检测其他客户端是否正在阻塞升级发生,如果其他客户端在versionchange事件后仍保持与数据库的连接,则会触发阻塞事件。

// 创建基于新版本的数据库连接,触发versionchange事件
const request = indexedDB.open("library", 4);

request.onblocked = function() {
  // 检测到有数据库升级事件,执行保存数据的逻辑
  blockedTimeout = setTimeout(function() {
    displayMessage(
        "Upgrade blocked - Please close other tabs displaying this site."
    );
  }, 1000);};

request.onupgradeneeded = function(event) {
  clearTimeout(blockedTimeout);
  // 一些善后工作
  hideMessage();
  // ...
};
function hideMessage() {
  // Hide a previously displayed message.
} 

2. 结构

数据库的关键属性name(数据库名称),数据库名称对大小写敏感。

2.1 Database

每个域名下可以创建多个数据库,每个数据库具有零个或多个Object Store(可以理解为数据表),这些Object Store被用来存储数据。

Database有name属性,是一个常量,在数据库的生命周期内保持不变。

Database有version属性,数据库第一次被创建时,version默认为0。一个数据库同一时刻只会存在一个版本,使用upgrade transaction(触发数据库升级的事务)是更改数据库版本的唯一方式。

js通过建立连接来操作数据库,同一时刻,对于给定的数据库可能存在多个连接。只能建立与当前域名作用域下的数据库的连接。

每个连接有一个初始化为false的close pending flag。当连接关闭后会被设置为true

在特殊情况下,浏览器端会关闭连接,例如由于无法访问文件系统,权限更改,或数据库被删除等。如果发生这种情况,浏览器端必须运行关闭数据库连接,并将连接close pending flag设置为true

一个数据库连接可以操作该数据库下的Object Store,如果尝试升级或删除数据库,会触发打开连接的versionchange事件,这使得连接有机会关闭以允许升级或删除数据库继续进行。

2.2 Object Store

Object Store是数据库存储的主要机制,一个Object Store包含存储在该store中的一系列记录,每个记录包含一个key和一个value,这些数据以key升序的规则存储。

一个Object Store有以下特性:

  • name

Object Storename属性,Object store的命名是不能重复的。

  • 主键

一个Object Store对象有一个可选的keypath,如果一个store设置了主键,意思它要使用 in-line-keys 否则它将使用out-of-line keys

  • 当一个Store(数据集合)被创建的时候,它可以使用一个自动生成的主键来区分每一条记录。如果一个数据库没有设置主键,将会为他引入key生成器来区分插入到这个store的记录。一个store的主键有三个来源:

    • 主键生成器: 每次新的记录插入的时候为其生成一个自增的数字。
    • 主键可以通过keyPath来主动设置。
    • 主键可以根据存储的数据来明确的区分。例如: 日期对象,文件对象,二进制对象,图片对象等等。
  • Object Store handle

  1. js不直接与Object Store交互,而是通过事务中通过Object Store handle间接访问。可以针对同一个Object Store创建多个事务,一个事务仅能有一个关联的Object Store。

一个🌰(在线地址):

const request = indexedDB.open("objectStoreHandle");
let db;
request.onsuccess = function(event) {
  db = request.result;
  console.log(event, 'success-----')
}
request.onerror = function(event) {
  console.log(event, 'error------')
}
request.onblocked = function(event) {
  console.log(event, 'error------')
}
request.onupgradeneeded = function(event) {
   // 在这个upgrade transaction里可以创建数据表和管理数据表的索引
  const db1 = event.target.result;
  // object store handle
  const objectStore = db1.createObjectStore("idbobjectStore", {  autoIncrement: true });
    // 使用 object store handle 创建索引
    objectStore.createIndex("name", "name", { unique: false });
    objectStore.createIndex("age", "age");
}

setTimeout(() => {
  const transaction = db.transaction("idbobjectStore", "readwrite");
  transaction.oncomplete = function(event) {
    console.log('transaction committed-----', event)
  }
  transaction.onerror = function (event) {
    console.log('transaction error-----', event)
  }
  transaction.onabort = function (event) {
    console.log('transaction abort-----', event)
  }
  console.log(transaction, transaction.parent, 'transaction-----')
  // object store handle
  const objStore = transaction.objectStore("idbobjectStore");
  // 可以调用 objStore的属性和方法操作数据表里存储的数据
  console.log(objStore, 'objStore-----')
}, 2000)
console.log(request, 'request----') 

2.3 Values

支持存储的数据类型

  • String

  • Number

  • Object

  • Array

  • Date Object

  • File Object

  • Blob Object

  • ImageData Object

value的存取都是基于值而不是引用,所以后面基于该对象的修改都不会影响数据库里存的对应记录。

2.4 Keys

为了更高效的存取数据,object store中的记录都是按照主键 or 索引 升序存储。

一个key也有一个关联的值,如果类型是number或者date,则为不受限制的双精度浮点数(不受限制的双精度类型是浮点数类型,对应于所有可能的双精度64位的IEEE754浮点数,finitenon-finite和特殊的非数字值NaN的集合)。

ECMAScript 类型都是合法的keys

  • 数字类型: 除了NaN都支持,包括 Infinity-Infinity

  • 日期类型:除非这个DataValueinter slotNaN

  • 字符串类型

  • ArrayBuffer 对象

  • 数组对象

keys的比较逻辑:

-infinity 是一个key的最小值,比较逻辑是:

数字类型的键值 < 日期类型的键值,日期类型的键值 < 字符串类型的键值,字符串类型的键值 < 二进制类型的键值,二进制类型的键值 < 数组类型的键值,所以可以设定数据库的最大值是[],最小值是-infinity

二进制keys 的比较范围是无符号数 0-255(包含首尾)而不是有符号数-128-127(包含首尾)

2.5 Key Path

主键是一个字符串或者一个字符串列表,能够唯一确定一条数据记录。合法的主键:

  • 一个空字符串

  • 一个标识符,符合ECMAScript语法规范的标识符

  • 逗号分隔的标识符

  • 一个非空的字符串数组,字符串符合以上约束。

空格不允许作为一个组件

2.6 Index

我们可以借助索引来实现对数据库更快的查询、更新和删除。

索引具有以下属性:

  • name

一个Store创建的索引名称应该是确定且唯一的,

  • unique

如果设置该属性为true,则插入或者更新的索引值和数据库中已有的记录重复会失败。

  • multiEntry

如果设置为false,则会为其创建一个数组索引。如果设置为true,则一个记录会为这个数组索引的每个子元素创建索引。

js不直接和索引交互,而是通过创建事务来操作索引。一个事务只能绑定一个index处理函数。

一个index处理函数有一个名字,这个名字知道一个upgrade 事务触发前都是有效的。

2.7 事务

我们通过创建事务来读/写数据库中的数据。

事务可以保证我们多个数据库读写操作的有序进行,一个事务可能被用来存储大量数据或者有条件去修改一些数据,事务表示一组原子的、持久的数据访问和数据变更操作。

一个事务有一个作用域,它是一个事务可以与之交互的对象集合。这个作用域会一直保持有效直到数据库版本升级的事务触发。一个事务有以下模式:

  • readonly

这种类型的事务只允许读取数据,数据库打开,即可创建这种类型的事务,这种类型的事务有一个优势就是可以同一时间创建作用域有重叠甚至在同一个作用域的多个事务。

  • readwrite

这种类型的事务允许读取、修改和删除数据。数据库打开,即可创建这种类型的事务,这种类型的事务不支持同一时间创建多个作用域有重叠的多个事务。

  • versionchange

这种类型的事务允许读取、修改和删除数据。也能够创建和删除Stores(数据表)和索引。这种类型的事务会在一个upgradeneeded事件被触发的时候自动创建,不能够手动创建。

一个事务有一个持久性标记,这个持久性标记有以下取值:

  • strict

只有在验证所有未完成的更改都已成功写入持久存储介质后,用户代理才可以认为事务已成功提交。

  • relaxed

一旦所有未完成的修改都成功写入存储介质后,用户代理可以任务事务已成功提交,无需后续验证。

  • default

用户代理对storage bucket使用其默认的持久性行为,这是事务在没有另外指定下的默认值。

鼓励 Web 应用程序对临时数据(例如缓存或快速更改的记录)使用relaxed模式,而在降低数据丢失风险大于对性能和电源的影响的情况下使用strict模式。鼓励实现权衡来自应用程序的持久性提示与对用户和设备的影响。

事务还有 waitUntil 方法?

2.7.1 事务的生命周期

一个事务有以下状态:

  • active

当事务首次创建,或者基于此事务派发一个事件时该事务会进入这个状态,当事务处于这个状态时,可以针对该事务发起新请求。

  • inactive

事务在其创建后控制权返回到事件循环之后,以及不再有新的请求发起时,就处于这种状态。

当事务处于此状态时,不能对事务提出任何请求。

  • committing

一旦与事务关联的所有请求都完成后,事务将在尝试提交时进入此状态。当事务处于此状态时,不能对事物提出任何请求。

  • finished

一旦一个事物已经提交或者被迫中止,就会进入这个状态。当事务处于此状态时,不能对事物提出任何请求。

我们能够创建一个较长时间的事务,但这不是推荐的做法,因为它可能会带来不好的用户体验。一个事务的生命周期如下:

  1. 一个事务被创建基于对应的模式和作用域,一个事务在创建的时候就进入激活状态。

  2. 当一个实现能够对下面定义的事务范围和模式进行约束时,实现必须将任务排队以启动异步事务。

一旦事务创建,就可以开始执行针对事务放置的请求,请求必须按照他们针对交易的顺序执行,他们的结果必须按照针对特定事务请求的顺序返回,无法保证不同的事务中请求的返回顺序。

事务模式保证不同事务的两个请求可以以任何顺序执行,而不会影响存储在数据库中的结果数据。

当处理与事务关联的每个请求时,将触发success 或 error 事件。在调度事件时,事务状态设置为active状态,允许针对事务发出其他请求。一旦事件派发完成,事务的状态将再次设置为inactive状态。

一个事务能够在结束前的任何时间点被终止,不管这个事务当前是active状态还是未开始状态。

当事务中止时(有错误发生),必须撤消(回滚)在该事务期间对数据库所做的任何更改。这包括对对象存储内容的更改以及对象存储和索引的添加和删除。

当针对事务的所有请求都已完成并处理其返回的结果、没有针对事务提出新请求且事务尚未中止时,实现必须尝试提交事务

事务开始之前,发起的这些请求不会执行,但是事务会跟踪记录这些请求的顺序。

就是说一个事务会创建一个事件循环队列,也会对应一个事件循环清除队列。

事务被成功commit后,complete事件会被触发。事务被abort时,abort事件会被触发。

2.7.2 事务调度

以下约束定义了何时开启一个事务:

  • 一个只读事务能够开始的条件:没有基于相同Object Store的读写事务在其之前创建且不处于finished状态(

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: IndexedDB 是一种浏览器内置的 NoSQL 数据库,允许您在浏览器中存储和检索数据。下面是一个基本的 IndexedDB 示例: ```javascript // 打开 IndexedDB 数据库 var request = window.indexedDB.open("myDatabase", 1); // 处理成功和失败事件 request.onerror = function(event) { console.log("Database error: " + event.target.errorCode); }; request.onsuccess = function(event) { console.log("Database opened successfully"); var db = event.target.result; // 添加数据到数据库 var transaction = db.transaction(["customers"], "readwrite"); var objectStore = transaction.objectStore("customers"); var customer = { name: "John", email: "[email protected]" }; var request = objectStore.add(customer); request.onsuccess = function(event) { console.log("Customer added to database"); }; // 从数据库中检索数据 var transaction = db.transaction(["customers"], "readonly"); var objectStore = transaction.objectStore("customers"); var request = objectStore.get(1); request.onerror = function(event) { console.log("Error retrieving customer"); }; request.onsuccess = function(event) { var customer = event.target.result; console.log("Retrieved customer: " + customer.name); }; }; // 创建数据库架构 request.onupgradeneeded = function(event) { var db = event.target.result; var objectStore = db.createObjectStore("customers", { keyPath: "id", autoIncrement: true }); objectStore.createIndex("name", "name", { unique: false }); objectStore.createIndex("email", "email", { unique: true }); }; ``` 这段代码会创建一个名为 "myDatabase" 的 IndexedDB 数据库,其中包含一个名为 "customers" 的对象仓库,用于存储客户数据。该代码还演示了如何向数据库添加数据、从数据库检索数据以及如何创建对象仓库和索引。 ### 回答2: IndexedDB 是一种在 web 浏览器中使用的客户端存储数据库。它可以用于存储大量结构化数据,并且能够在离线状态下进行访问。 一个使用 IndexedDB 的实例可以是一个在线笔记应用。在这个应用中,用户可以创建、编辑和删除笔记。使用 IndexedDB,应用可以将这些笔记存储在本地,以便用户在断网或者关闭浏览器后仍然可以访问。 在这个应用中,首先需要创建一个数据库,用于存储笔记的数据。然后,可以创建一个 object store,用于存储每一条笔记的数据。每一条笔记可以由一个对象表示,其中包含标题、内容和日期等属性。 当用户创建新的笔记时,应用会将该笔记的数据添加到 object store 中。当用户编辑或者删除笔记时,应用会相应地更新或者删除 object store 中对应的数据。 当用户重新打开应用时,可以从 IndexedDB 中获取存储的笔记数据,并在界面上展示出来。用户可以通过界面进行编辑,应用会即时地更新 IndexedDB 中的数据。 除了基本的增删改查操作,IndexedDB 还提供了强大的查询功能。通过使用索引,可以高效地搜索和过滤数据。例如,可以根据标题关键字进行搜索,并返回包含该关键字的所有笔记。 总而言之,IndexedDB 提供了一种方便可靠的方式来在 web 浏览器中存储大量结构化数据。通过使用它,可以实现很多实用的应用,如在线笔记应用等。 ### 回答3: IndexedDB 是 HTML5 标准中的一种客户端数据库,可以在浏览器中存储和操作大量的结构化数据。下面是一个使用 IndexedDB 的示例: 首先,在 JavaScript 中创建一个 IndexedDB 数据库,并指定数据库的名称和版本号: ```javascript let request = indexedDB.open('myDatabase', 1); ``` 然后,在打开数据库成功的回调函数中创建一个对象存储空间(类似于表): ```javascript request.onsuccess = (event) => { let db = event.target.result; let objectStore = db.createObjectStore('myObjectStore', { keyPath: 'id' }); }; ``` 现在,可以向对象存储空间中添加数据项: ```javascript request.onsuccess = (event) => { let db = event.target.result; let transaction = db.transaction('myObjectStore', 'readwrite'); let objectStore = transaction.objectStore('myObjectStore'); let data = { id: 1, name: 'John', age: 25 }; let request = objectStore.add(data); request.onsuccess = (event) => { console.log('Data added successfully'); }; request.onerror = (event) => { console.error('Error adding data'); }; }; ``` 还可以使用索引检索数据: ```javascript request.onsuccess = (event) => { let db = event.target.result; let transaction = db.transaction('myObjectStore', 'readonly'); let objectStore = transaction.objectStore('myObjectStore'); let index = objectStore.index('name'); let request = index.get('John'); request.onsuccess = (event) => { let data = event.target.result; console.log(`Name: ${data.name}, Age: ${data.age}`); }; request.onerror = (event) => { console.error('Error retrieving data'); }; }; ``` 此外,还可以更新和删除数据、创建和删除索引等等。这只是一个简单的使用 IndexedDB 的示例,实际应用中可以根据需求进行更复杂的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值