# IndexedDB

IndexedDB主要用于在浏览器存储大量的数据, Cookie(最多4kb)和localStorage(2.5MB~10MB不等)都不适合存储大量数据。

IndexedDB可以理解为浏览器提供的本地数据库, 可以被js脚本创建和操作, 能提供查找接口, 创建索引。就数据库类型而言, 可能更接近于NoSQL数据库,不是关系型的, 可能更像是redis那种键值型的。

# 1、IndexedDB特点

  1. 键值对存储。 IndexedDB内部采用对象仓库存放数据。 所有类型的数据都可以直接存入, 包括js对象。对象仓库中,数据以键值对的形式保存, 每一个数据就都有对应的主键,主键是独一无二的, 重复的话会抛出错误。
  2. 异步,IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作。然后localStorage 是同步的(虽然我们可以通过一些办法把设置值的操作变成异步延后)。异步设计是为了防止大量数据的读写, 阻塞网页的渲染更新。
  3. 支持事务, IndexedDB支持事务(transaction), 这样子如果有多个操作步骤, 只要有一个失败, 事务就会取消, 数据库会回滚到事务发生之前的状态, 不会存在部分修改的情况。
  4. 同源限制, IndexedDB受同源限制, 每一个数据库对应创建它的域名, 网页只能访问自身域名下的数据库, 而不能访问跨域的数据库。
  5. 存储空间大, IndexedDB的存储空间比LocalStorage大的多, 一般来说不少于250MB,甚至没有上限。
  6. 支持二进制存储。 IndexedDB 不仅可以存储字符串, 还可以存储二进制数据(ArrayBuffer对象和Blob对象)。

# 2、基本概念

IndexedDB 是一个比较复杂的 API,涉及不少概念。它把不同的实体,抽象成一个个对象接口。学习这个 API,就是学习它的各种对象接口。

  • 数据库:IDBDatabase 对象
  • 对象仓库:IDBObjectStore 对象
  • 索引: IDBIndex 对象
  • 事务: IDBTransaction 对象
  • 操作请求:IDBRequest 对象
  • 指针: IDBCursor 对象
  • 主键集合:IDBKeyRange 对象
  1. 数据库

    数据库是一系列相关数据的容器,每个域名都可以新建任意多个数据库。

    IndexedDB 数据库有版本的概念。同一个时刻,只能有一个版本的数据库存在。如果要修改数据库结构(新增或删除表、索引或者主键),只能通过升级数据库版本完成。

  2. 对象仓库

    每个数据库包含若干个对象仓库(ObjectStore), 他类似于MySql这种数据库的表格

  3. 数据记录

    对象仓库保存的是数据记录, 每条记录类似于关系型数据库的行, 但是只有主键和数据体两部分。主键用于建立默认的索引, 必须是不同的, 否则就会报错, 主键可以是数据记录里面的一个属性,可以指定为一个递增的整数编号。

    数据体可以是任意的数据。

  4. 索引

    为了加速数据的检索, 可以在对象仓库里面, 为不同的属性建立索引

  5. 事务

    数据记录的读写和删改,都要通过事务完成。事务对象提供errorabortcomplete三个事件,用来监听操作结果。

# 3、操作流程

# 3.1 打开数据库

使用IndexedDB的第一步是打开数据库, 使用indexedDB.open()方法

const openDBRequest = indexedDB.open('test', 3);

第一个参数是数据库的名字, 如果指定的名字不存在 就会新建数据库。 第二个参数是整数, 表示数据库的版本,打开时, 默认为当前版本, 新建数据库时, 默认是1。indexedDB.open()方法返回一个 IDBRequest 对象。这个对象通过三种事件errorsuccessupgradeneeded,处理打开数据库的操作结果

  1. error 事件

    openDBRequest.onerror = function (e) {
      console.log('onerror', e);
    };
    

    表示打开数据库失败

  2. onupgradeneeded 事件

    openDBRequest.onupgradeneeded = function (e) {
      console.log('onupgradeneeded', e);
      // db = e.target.result
    };
    

    如果指定的版本号, 大于数据库的实际版本号, 就会发生数据库升级事件

  3. success事件

    openDBRequest.onsuccess = function (e) {
      console.log('onsuccess', e);
      // db = e.target.result
      // or
      db = openDBRequest.result;
      }
    };
    

    表示数据库打开成功, 可以通过 result 属性获取 IDBDatabse对象。 如果是第一次打开或者版本升级 也可以在 onupgradeneeded事件获取

# 3.2 新建数据库

新建数据库与打开数据库是同一个操作。如果指定的数据库不存在,就会新建。不同之处在于,后续的操作主要在upgradeneeded事件的监听函数里面完成,因为这时版本从无到有,所以会触发这个事件。

通常, 新建数据库以后, 第一件事是新建对象仓库

TIP

对象仓库(ObjectStore)相当于关系型数据库的表

openDBRequest.onupgradeneeded = function (e) {
  console.log('onupgradeneeded', e);
  db = openDBRequest.result;
  // 判断是不是存在一张叫做person的表格, 不存在就新建
  let objectStore;
  if (!db.objectStoreNames.contains('person')) {
    objectStore = db.createObjectStore('person', {
      keyPath: 'id' // 设置主键是id, 可以用于建立默认的索引
      // autoIncrement: true // 自动生成主键, 是一个递增的整数
    });
    //   新建对象仓库以后, 就可以建立对应的索引
    // 	 说明该属性是否包含重复的值	
    //   objectStore.createIndex('name', 'name', { unique: false });
  }
};

上面代码中,数据库新建成功以后,新增一张叫做person的表,主键是id

# 3.3 新增数据

往对象仓库写入数据记录, 这需要用事务完成。

let id = 600;
function addPersonData() {
  const personTransaction = db.transaction(['person'], 'readwrite');
  const personObjectStore = personTransaction.objectStore('person');
  // addHandler: IDBRequest
  const addDBRequest = personObjectStore.add({
    id: id++,
    name: '张三',
    age: parseInt(Math.random() * 10) + 10,
    email: 'xxxx@123.com'
  });
  console.log(addDBRequest);
  addDBRequest.onsuccess = function (e) {
    console.log('数据写入成功');
  };
  addDBRequest.onerror = function (e) {
    console.log('数据写入失败', e);
  };
}
const personTransaction = db.transaction(['person'], 'readwrite');

新建一个事务, 需要指定对象仓库名称和操作模式(readonly或者readwrite)。新建事务以后, 通过

const personObjectStore = personTransaction.objectStore('person');

objectStore 方法拿到对象仓库, 再通过对象仓库的add方法, 向表格写入一条数据(注意主键不能重复, 重复就写不进去)。

# 3.4 读取数据

读取数据也是用过事务完成

function readPersonData() {
  const personTransaction = db.transaction(['person'], 'readonly');
  const personObjectStore = personTransaction.objectStore('person');
  const readIDBRequest = personObjectStore.get(500);
  readIDBRequest.onerror = e => {
    console.log('数据读取失败');
  };
  readIDBRequest.onsuccess = () => {
    const { result } = readIDBRequest;
    if (result) {
      console.log('数据读取成功');
      console.table(result);
    } else {
      console.log('没有读到数据');
    }
  };
}

const readIDBRequest = personObjectStore.get(500); 用于读取数据, 参数是主键的值。

# 3.5 遍历数据

遍历数据需要使用指针对象 IDBCursor

function readAll() {
  const personTransaction = db.transaction(['person'], 'readonly');
  const personObjectStore = personTransaction.objectStore('person');
  const cursorIDBRequest = personObjectStore.openCursor();
  const result = [];
  cursorIDBRequest.onsuccess = e => {
    const { result: cursor } = cursorIDBRequest;
    if (cursor) {
      console.log(cursor.key, cursor.value);
      result.push(cursor.value);
      cursor.continue();
    } else {
      console.log('没有更多数据了');
      console.log(result);
    }
  };
}

ObjectStore.openCursor() 获取 IDBCursor

# 3.6 更新数据

ObjectStore.put() 用于更新数据。

function updateData() {
  const personTransaction = db.transaction(['person'], 'readwrite');
  const personObjectStore = personTransaction.objectStore('person');
  const putIDBRequest = personObjectStore.put({
    id: 500,
    name: '李四'
  });
  putIDBRequest.onsuccess = function () {
    console.log('更新成功');
  };
  putIDBRequest.onerror = function () {
    console.log('更新失败');
  };
}

上面代码中,put()方法自动更新了主键为 500 的记录。

# 3.7 删除数据

使用ObjectStore.delete删除数据, 参数是主键值

function deleteData(id = 500) {
  console.log(id);
  const personTransaction = db.transaction(['person'], 'readwrite');
  const personObjectStore = personTransaction.objectStore('person');
  const deleteIDBRequest = personObjectStore.delete(id);
  deleteIDBRequest.onsuccess = function () {
    console.log('删除成功');
  };
  deleteIDBRequest.onerror = function () {
    console.log('删除失败');
  };
}

# 3.8 使用索引

索引的意义在于,可以让你搜索任意字段,也就是说从任意字段拿到数据记录。如果不建立索引,默认只能搜索主键(即从主键取值)。

function useIndex() {
  const personTransaction = db.transaction(['employee'], 'readonly');
  const personObjectStore = personTransaction.objectStore('employee');
  const index = personObjectStore.index('name');
  console.log(index);
  const request = index.get('李四');
  request.onsuccess = function () {
    const { result } = request;
    console.log(result);
  };
}

比如我们使用了 name 作为索引,可以看下图

我们多了一列name的索引

# 4、indexedDB对象

# 4.1 indexedDB.deleteDatabase()

用于删除一个数据库, 参数为数据库的名字。

function deleteDb() {
  const request = indexedDB.deleteDatabase('test3');
  request.onsuccess = e => {
    console.log('onsuccess', e);
  };
  request.onerror = e => {
    console.log('onerror', e);
  };
}

删除不存在的数据库不会报错

# 5、IDBRequest 对象

IDBRequest对象表示打开的数据库连接,indexedDB.open()indexedDB.deleteDatabase() 都会返回这个对象。数据库的操作都是通过这个对象完成的。

这个对象的所有操作都是异步操作,要通过readyState属性判断是否完成,如果为pending就表示操作正在进行,如果为done就表示操作完成,可能成功也可能失败。

操作完成以后,触发success事件或error事件,这时可以通过result属性和error属性拿到操作结果。如果在pending阶段,就去读取这两个属性,是会报错的。

IDBRequest 对象有以下属性。

  • IDBRequest.readyState:等于pending表示操作正在进行,等于done表示操作正在完成。
  • IDBRequest.result:返回请求的结果。如果请求失败、结果不可用,读取该属性会报错。
  • IDBRequest.error:请求失败时,返回错误对象。
  • IDBRequest.source:返回请求的来源(比如索引对象或 ObjectStore)。
  • IDBRequest.transaction:返回当前请求正在进行的事务,如果不包含事务,返回null
  • IDBRequest.onsuccess:指定success事件的监听函数。
  • IDBRequest.onerror:指定error事件的监听函数。

IDBOpenDBRequest 对象继承了 IDBRequest 对象,提供了两个额外的事件监听属性。

  • IDBOpenDBRequest.onblocked:指定blocked事件(upgradeneeded事件触发时,数据库仍然在使用)的监听函数。
  • IDBOpenDBRequest.onupgradeneededupgradeneeded事件的监听函数。

# 6、IDBDatabase 对象

打开数据成功以后,可以从IDBOpenDBRequest对象的result属性上面,拿到一个IDBDatabase对象,它表示连接的数据库。后面对数据库的操作,都通过这个对象完成。

IDBDatabase 对象有以下属性。

  • IDBDatabase.name:字符串,数据库名称。
  • IDBDatabase.version:整数,数据库版本。数据库第一次创建时,该属性为空字符串。
  • IDBDatabase.objectStoreNames:DOMStringList 对象(字符串的集合),包含当前数据的所有 object store 的名字。
  • IDBDatabase.onabort:指定 abort 事件(事务中止)的监听函数。
  • IDBDatabase.onclose:指定 close 事件(数据库意外关闭)的监听函数。
  • IDBDatabase.onerror:指定 error 事件(访问数据库失败)的监听函数。
  • IDBDatabase.onversionchange:数据库版本变化时触发(发生upgradeneeded事件,或调用indexedDB.deleteDatabase())。

IDBDatabase 对象有以下方法。

  • IDBDatabase.close():关闭数据库连接,实际会等所有事务完成后再关闭。
  • IDBDatabase.createObjectStore():创建存放数据的对象仓库,类似于传统关系型数据库的表格,返回一个 IDBObjectStore 对象。该方法只能在versionchange事件监听函数中调用。
  • IDBDatabase.deleteObjectStore():删除指定的对象仓库。该方法只能在versionchange事件监听函数中调用。
  • IDBDatabase.transaction():返回一个 IDBTransaction 事务对象。

具体的方法使用上面其实已经都做了介绍了

# 7、IDBObjectStore对象

IDBObjectStore 对象对应一个对象仓库(object store)。IDBDatabase.createObjectStore()方法返回的就是一个 IDBObjectStore 对象。

IDBDatabase 对象的transaction()返回一个事务对象,该对象的objectStore()方法返回 IDBObjectStore 对象,因此可以采用下面的链式写法。

db.transaction(['test'], 'readonly')
  .objectStore('test')
  .get(X)
  .onsuccess = function (e) {}

# 7.1 属性

  • IDBObjectStore.indexNames:返回一个类似数组的对象(DOMStringList),包含了当前对象仓库的所有索引。
  • IDBObjectStore.keyPath:返回当前对象仓库的主键。
  • IDBObjectStore.name:返回当前对象仓库的名称。
  • IDBObjectStore.transaction:返回当前对象仓库所属的事务对象。
  • IDBObjectStore.autoIncrement:布尔值,表示主键是否会自动递增。

# 7.2 方法

(1)IDBObjectStore.add()

IDBObjectStore.add()用于向对象仓库添加数据,返回一个 IDBRequest 对象。该方法只用于添加数据,如果主键相同会报错,因此更新数据必须使用put()方法。

objectStore.add(value, key)

该方法接受两个参数,第一个参数是键值,第二个参数是主键,该参数可选,如果省略默认为null

(2)IDBObjectStore.put()

IDBObjectStore.put()方法用于更新某个主键对应的数据记录,如果对应的键值不存在,则插入一条新的记录。该方法返回一个 IDBRequest 对象。

(3)IDBObjectStore.clear()

IDBObjectStore.clear()删除当前对象仓库的所有记录。该方法返回一个 IDBRequest 对象。不需要参数

(4)IDBObjectStore.delete()

IDBObjectStore.delete()方法用于删除指定主键的记录。该方法返回一个 IDBRequest 对象。参数为主键的值

(5)IDBObjectStore.count()

IDBObjectStore.count()方法用于计算记录的数量。该方法返回一个 IDBRequest 对象。

不带参数时,该方法返回当前对象仓库的所有记录数量。如果主键或 IDBKeyRange 对象作为参数,则返回对应的记录数量。

(6)IDBObjectStore.getKey()

IDBObjectStore.getKey()用于获取主键。该方法返回一个 IDBRequest 对象。

objectStore.getKey(key)

该方法的参数可以是主键值或 IDBKeyRange 对象。

(7)IDBObjectStore.get()

IDBObjectStore.get()用于获取主键对应的数据记录。该方法返回一个 IDBRequest 对象。

(8)IDBObjectStore.getAll()

DBObjectStore.getAll()用于获取对象仓库的记录。该方法返回一个 IDBRequest 对象。

获取所有的记录也可以使用指针Cursor

(9)IDBObjectStore.getAllKeys()

IDBObjectStore.getAllKeys()用于获取所有符合条件的主键。该方法返回一个 IDBRequest 对象。

// 获取所有记录的主键
objectStore.getAllKeys()

// 获取所有符合条件的主键
objectStore.getAllKeys(query)

// 指定获取主键的数量
objectStore.getAllKeys(query, count)
上次更新: 1/22/2025, 9:39:13 AM