indexedDB入门

IndexedDB入门指南

简介

IndexedDB 是一种底层 API(浏览器底层已经自带),用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 使用索引实现对数据的高性能搜索。虽然 Web Storage 在存储较少量的数据很有用,但对于存储更大量的结构化数据来说力不从心。而 IndexedDB 提供了这种场景的解决方案。

关键概念及用法

IndexedDB是一个基于事务型 数据库系统,基于事务 这一点非常关键,是使用好IndexedDB的关键所在。在IndexedDB里没有其他数据库中表的概念,它是一个基于JavaScript的面向对象的数据库。也就是说IndexedDB并不是像其他数据库用一张表存储相关数据的模式,而是用一个对象来进行存储,可以简单的将IndexedDB中的对象理解为其它数据库的表。

注意:使用 IndexedDB 执行的操作是异步执行的,以免阻塞应用程序。因此,通过事务对数据执行操作后都会返回一个IDBRequest 实例对象request,如果 request 对象成功执行了,结果可以通过 result 属性访问到,并且该 request 对象上会触发 success 事件。如果操作中有错误发生,一个 error 事件会触发,并且会通过 result 属性抛出一个异常。

如何理解IndexedDB的事务

IndexedDB是一个基于事务的数据库,那么这意味着我们对数据的所有操作都必须通过事务 来完成。而事务的存在则保证了我们对数据操作的原子性一致性隔离性持久性

  • 原子性:意味着事务中的所有操作要么全部成功,要么全部失败;
  • 一致性:确保了数据库从一个有效状态转移到另一个有效状态;
  • 隔离性:有效防止了并发事务相互的干扰;
  • 持久性:保证了事务一旦完成,其结果将持久的保存在数据库中。

那既然我们对数据的所有操作都必须通过事务完成,那么请记住 我们对数据操作之前的首要目的 那肯定就是获取事务

简单说一下我自己理解的indexedDB数据库的使用逻辑:

1.拿到数据库对象

2.通过数据库对象创建事务获取事务对象

3.通过事务对象得到对应的仓库对象

4.通过仓库对象对数据进行操作并得到一个请求对象request,里面包含了本次操作的结果

5.在请求对象request对象的onsuccess回调中处理请求成功的结果,在onerror回调中处理失败的结果。

由以上步骤可以知道我们对indexedDB数据库进行一系列操作是比较麻烦的,因此有一些三方库基于indexedDB产生了,但是为了加深我们对indexedDB的了解,我们可以自己封装一些常用的操作,我会在后面给到相关的代码和封装逻辑。

IndexedDB数据库的几个关键组成

IDBDatabase 对象

简称db,可以将其理解为数据库的实例对象,这是IndexedDB中提供的一个接口用于和数据库进行连接;对数据库的任何操作都离不开数据库实例对象db,因此对一个数据库进行操作的第一件事就是获取db对象。

常用方法介绍

  • IDBDatabase.close() :在一个单独线程中关闭数据库连接并立即返回。

  • IDBDatabase.createObjectStore(storeName,optionalParameters) :在该数据库连接下创建并返回一个新的objectStore。

    • storeName:仓库名称,注意在MDN中介绍的创建一个空名称的仓库是被允许的。
    • optionalParameters(可选属性):是一个参数对象,里面包含keyPathautoIncrement 两个属性。
      • keyPath :keyPath是主键,一旦指定keyPath后,后续添加的数据就必须要包含keyPath属性。
      • autoIncrement :其值为布尔值,默认为false。当创建一个对象存储(ObjectStore)时,可以设置autoIncrement选项为true。这样,每当你向该对象存储添加一个新对象而没有指定主键(key)时,IndexedDB会自动为这个新对象生成一个唯一的主键值。这个主键值会从现有的最大主键值开始递增,确保每个对象都有一个独一无二的标识符。这个特性在处理大量数据时非常有用,因为它减少了手动管理主键的复杂性,同时也避免了主键冲突的问题。
  • IDBDatabase.deleteObjectStore(storeName):根据给定的仓库名称,删除db实例对应的数据库连接下的仓库。

  • IDBDatabase.transaction(storeNames,mode,options) :我们一直说indexedDB是一个事务型数据库,那么事务就显得尤为重要,而transaction 接口的返回值 就是当前仓库的事务对象(IDBTransaction)!!!

    • storeNames :新事务范围内的对象存储的名称,可以声明为字符串数组 ,也可以声明为一个字符串

      • 字符串数组:也就是说你可以为多个数据仓库同时开启一个事务对象上下文中读取和写入多个存储空间,而不是为每一个仓库都开启一个事务,大大提高了数据操作的效率。
      • 字符串:只需要访问一个对象存储空间时可以将其声明为字符串。

      以下两种声明方式是等效的:

      js 复制代码
      db.transaction(["my-store-name"]);
      db.transaction("my-store-name");
    • mode :指定事务执行过程中的操作模式。理解 :我们最终通过事务是对仓库中的数据进行操作的对吧,那么对数据操作就有不同的模式,比如只读readonly 、读写readwrite ,因此我们在拿到事务对象的时候就要指定我们的事务操作模式,默认是只读readonly

      • readonly:只读,进行数据访问时用该模式。
      • readwrite:读写,对数据进行增、删、改时用该模式。
    • options :不常用,用到时可参考MDN官网[IDBDatabase.transaction(storeNames,mode,options) ](IDBDatabase: transaction() 方法 - Web API 接口 |MDN的 (mozilla.org))

常用属性介绍

IDBFactory对象

IndexedDB APIIDBFactory 接口让程序可以异步存取 indexed databases。window.indexedDB 对象实现了这个接口。你可以通过这个对象而不是直接使用 IDBFactory接口打开------创建或者连接------和删除一个数据库。

接口介绍

  • window.indexedDB.open(name,version):打开指定名称和版本号的数据库,没有则创建。

    • name:数据库名称
  • version:数据库版本

  • window.indexedDB.deleteDatabase(name):删除数据库。

    js 复制代码
    const DBDeleteRequest = window.indexedDB.deleteDatabase("toDoList");
    
    DBDeleteRequest.onerror = (event) => {
      console.error("Error deleting database.");
    };
    
    DBDeleteRequest.onsuccess = (event) => {
      console.log("Database deleted successfully");
    
      console.log(event.result); // should be undefined
    };

IDBOpenDBRequest对象

在使用**IDBFactory.open** 方法后会打开一个数据库连接。本方法立即返回一个 IDBOpenDBRequest (en-US) 对象,但打开数据库的操作是异步执行的。

IDBOpenDBRequest 对象有一个onupgradeneeded属性较为关键,是一个回调函数,它会在数据库创建或升级(版本号升高)时触发!

js 复制代码
function openDB(dbName, storeInfo, version = 1) {
  return new Promise((resolve, reject) => {
    // 兼容浏览器
    let indexedDB =
      window.indexedDB ||
      window.mozIndexedDB ||
      window.webkitIndexedDB ||
      window.msIndexedDB;
    // 存储数据库对象
    let db;
    // 打开指定名称数据库,没有则创建
    const request = indexedDB.open(dbName, version);

    // 数据库更新的回调 -- 在数据库创建或升级(版本号升高)时触发
    request.onupgradeneeded = function (event) {
      console.log("数据库创建或升级,已触发onupgradeneeded");
      db = event.target.result; // 保存数据库对象
      let objectStore = db.createObjectStore(storeInfo.name, {
        keyPath: storeInfo.keyPath, // 主键
      });
      // 创建索引,indexedDB查询很不方便,因此常用的可以创建索引便于使用
      objectStore.createIndex("filesTree", "filesTree", { unique: false });
    };
  });
}

IDBTransaction 对象

IndexedDB APIIDBTransaction 接口使用事件处理程序属性在数据库上提供静态异步事务。所有数据的读取和写入都是在事务中完成的。使用 IDBDatabase 启动事务,使用 IDBTransaction 设置事务模式(例如,是 it 还是 ),并访问 IDBObjectStore 发出请求。您还可以使用对象来中止事务。readonly readwrite IDBTransaction

该api的主要作用就是获取数据库实例对象db的事务对象,以便进行后续操作,其常用属性可参考[IDBTransaction接口介绍 ](IDBTransaction - Web API 接口参考 | MDN (mozilla.org))

IDBObjectStore 对象

IndexedDB APIIDBObjectStore 接口表示数据库中的 一个 对象库 (object store) 。对象库中的记录根据其键值进行排序。这种排序可以实现快速插入,查找和有序检索。

IDBCursor对象

IndexedDB API 中的 IDBCursor 接口表示一个游标,类似于指针,用于遍历或迭代数据库中的多条记录。

游标有一个源,指示需要遍历哪一个索引或者对象存储区。它在所属区间范围内有一个位置,根据记录健(存储字段)的顺序递增或递减方向移动。游标使应用程序能够异步处理在游标范围内的所有记录。

你可以在同一时间拥有无数个游标。你总会获得表示给定游标的同样的 IDBCursor 对象。在基础索引或对象存储上执行操作。

属性

  • IDBCursor.source (只读):返回一个游标正在迭代的 IDBObjectStore 或者 IDBIndex 。这个方法永远不会返回一个空或者抛出异常,即使游标当前正在被迭代,已迭代超过其结束,再或者其事务没有处于活动状态。

  • IDBCursor.direction(只读):返回光标遍历方向。请查看 常数 中可能的值。

  • IDBCursor.key(只读):返回记录中游标位置的有效主键。如果游标在区间之外,将会设置成 undefined。游标主键可以是任意的数据类型(data type)。

  • IDBCursor.primaryKey(只读):返回游标当前有效的主键。如果游标当前正在被迭代或者已经在迭代在区间范围外,将会被设置成 undefined 。游标主键可以是任意的时间类型(data type)。

方法

  • IDBCursor.advance:设置光标向前移动位置的次数。

  • IDBCursor.continue:将游标按它的方向移动到下一个位置,到其健与可选健参数匹配的项。

  • IDBCursor.delete:返回一个 IDBRequest 对象,并且在一个单独的线程中,删除游标位置记录,而不改变游标的位置。这个可以用作删除一些特定的记录。

  • IDBCursor.update:返回一个 IDBRequest 对象,并且在一个单独的线程中,更新对象存储中当前游标位置的值。这个可以用来更新特定的记录。

IDBKeyRange对象

IndexedDB APIIDBKeyRange 接口表示一些数据类型上的键的连续间隔。可以使用一个键或某个范围的键从IDBObjectStoreIDBIndex 对象中检索记录。你也可以指定键的上界和下界来限制范围。例如,你可以遍历值范围 a - z 中的键的所有值。

简单来说:IDBKeyRange 可以先根据索引指定一个范围,将该范围内的数据汇总返回给游标对象供我们后续访问,提高了检索效率。

属性

方法

静态方法
实例方法

在了解了以上关键对象之后,咱们就开始正式进入indexedDB数据库使用的学习啦!之前我提到过,indexedDB数据库的使用是较为繁琐 的,因此我们在使用的时候都会将常用的方法进行封装

数据库的打开和创建

js 复制代码
/**
 * 打开数据库,没有该数据库则创建
 * @param {String} dbName 数据库名
 * @param {Object} storeInfo 仓库信息,是一个对象,包含name和keyPath属性
 * @param {Number} version
 */
function openDB(dbName, storeInfo, version = 1) {
  return new Promise((resolve, reject) => {
    // 兼容浏览器
    let indexedDB =
      window.indexedDB ||
      window.mozIndexedDB ||
      window.webkitIndexedDB ||
      window.msIndexedDB;
    // 存储数据库对象
    let db;
    // 打开指定名称数据库,没有则创建
    const request = indexedDB.open(dbName, version);
    // 成功的回调
    request.onsuccess = function (event) {
      db = event.target.result; // 数据库对象
      console.log(`数据库${dbName}打开成功`);
      // 返回数据库对象
      resolve(db);
    };
    // 失败的回调
    request.onerror = function (event) {
      console.log(`数据库${dbName}打开失败`);
    };
    // 数据库更新的回调 -- 在数据库创建或升级(版本号升高)时触发
    request.onupgradeneeded = function (event) {
      console.log("数据库创建或升级,已触发onupgradeneeded");
      db = event.target.result; // 保存数据库对象
      let objectStore = db.createObjectStore(storeInfo.name, {
        keyPath: storeInfo.keyPath, // 主键
      });
      // 创建索引,indexedDB查询很不方便,因此常用的可以创建索引便于使用
      objectStore.createIndex("filesTree", "filesTree", { unique: false });
    };
  });
}

数据库插入数据

js 复制代码
/**
 * 数据库插入数据
 * @param {IDBDatabase} db 数据库对象
 * @param {String} storeName 仓库名字
 * @param {Object} data 插入的数据,是一个对象,必须包含创建数据库索引时的那几个属性
 */
function addData(db, storeName, data) {
  // 获取事务对象 -- 由于是添加数据操作,因此事务操作模式为读写
  let transaction = db.transaction([storeName], "readwrite");
  // 获取仓库对象
  let objectStore = transaction.objectStore(storeName);
  // 进行添加操作拿到请求对象
  let request = objectStore.add(data);

  /*上述步骤熟悉后可简化为链式调用
    let request = db
      .transaction([storeName], "readwrite")
      .objectStore(storeName)
      .add(data);
    */

  request.onsuccess = function (event) {
    console.log(`数据${data}插入成功`);
  };
  request.onerror = function (event) {
    console.log(`数据${data}插入失败`, event);
  };
}

数据库更新数据

js 复制代码
/**
 * 数据库的更新数据和插入数据没多大区别,就是add方法变为了put方法
 * put方法在数据库中有这条数据时其作用是修改
 * 但是如果数据库中没有这条数据,则它可以向里面添加数据
 * @param {IDBDatabase} db
 * @param {String} storeName
 * @param {Object} data
 */
function updateData(db, storeName, data) {
  // 获取事务对象 -- 由于是更新,因此操作模式得是读写
  let transaction = db.transaction([storeName], "readwrite");
  // 获取仓库对象
  let objectStore = transaction.objectStore(storeName);
  // 进行数据更新操作并接受请求对象
  let request = objectStore.put(data);

  request.onsuccess = function (event) {
    console.log(`数据${data}更新成功`);
  };
  request.onerror = function (event) {
    console.log(`数据${data}更新失败`, event);
  };
}

通过主键删除数据

js 复制代码
/**
 * 通过主键删除数据
 * @param {IDBDatabase} db
 * @param {String} storeName
 * @param {*} keyPathValue
 */
function deleteByKeyPathValue(db, storeName, keyPathValue) {
  // 链式调用 -- 删除操作也改变了数据,因此事务对象操作模式得是读写
  let request = db
    .transaction([storeName], "readwrite")
    .objectStore(storeName)
    .delete(keyPathValue);
  request.onerror = function (event) {
    console.log("删除失败");
  };
  request.onsuccess = function (event) {
    console.log("删除成功");
  };
}

通过索引和游标结合删除数据

js 复制代码
/**
 * 通过索引和游标结合删除数据,本质是利用了游标的cursor.delete()方法删除当前项
 * @param {IDBDatabase} db
 * @param {String} storeName
 * @param {String} indexName
 * @param {any} indexValue
 */
function deleteByIndexAndCursor(db, storeName, indexName, indexValue) {
  // 拿到事务对象
  let transaction = db.transaction([storeName], "readwrite");
  // 拿到仓库对象
  let objectStore = transaction.objectStore(storeName);
  // 获取指定索引的游标请求对象
  let request = objectStore
    .index(indexName)
    .openCursor(IDBKeyRange.only(indexValue));

  request.onerror = function (event) {
    console.log("通过索引游标结合删除失败");
  };
  request.onsuccess = function (event) {
    // 获取游标对象
    let cursor = event.target.result;
    if (cursor) {
      // 删除当前项
      cursor.delete();
      // 游标指向下一项
      cursor.continue();
    } else {
      console.log("删除完毕");
    }
  };
}

通过主键查询数据

js 复制代码
/**
 * 通过主键查询数据(异步操作)
 * @param {IDBDatabase} db 数据库对象
 * @param {String} storeName 仓库名字
 * @param {String} key 数据库主键
 */
function getDataByKey(db, storeName, key) {
  return new Promise((resolve, reject) => {
    // 获取事务对象
    let transaction = db.transaction([storeName]);
    // 获取仓库对象
    let objectStore = transaction.objectStore(storeName);
    let request = objectStore.get(key); // 通过主键获取数据
    // 失败回调
    request.onerror = function (event) {
      console.log("数据读取失败");
    };
    // 成功回调
    request.onsuccess = function (event) {
      // 返回结果
      resolve(request.result);
    };
  });
}

通过游标查询数据

js 复制代码
/**
 * 通过游标读取数据相当于遍历该仓库,可以在遍历时进行筛选
 * 如果需要直接遍历所有数据的话可以通过getAll方法实现
 * @param {IDBDatabase} db
 * @param {String} storeName
 */
function getDataByCursor(db, storeName) {
  return new Promise((resolve, reject) => {
    let result = [];
    // 获取事务对象
    let transaction = db.transaction([storeName]);
    // 获取仓库对象
    let objectStore = transaction.objectStore(storeName);
    // 打开游标
    let request = objectStore.openCursor();

    // 游标打开成功的回调
    request.onsuccess = function (event) {
      // 获取游标处的数据,返回的是 IDBCursorWithValue 对象,可通过.value拿到其值
      let cursor = event.target.result;
      // 判断该游标指向的对象是否存在
      if (cursor) {
        // 存储值
        result.push(cursor.value);
        // 控制游标指向下一处
        cursor.continue();
      } else {
        resolve(result);
      }
    };
  });
}

通过索引查询数据

js 复制代码
/**
 * 通过索引查询数据,注意该方法只能查询出满足条件的第一条数据!
 * 且确保你已经在 request.onupgradeneeded 中创建了索引!
 * @param {IDBDatabase} db
 * @param {String} storeName
 * @param {String} indexName
 * @param {any} indexValue
 */
function getDataByIndex(db, storeName, indexName, indexValue) {
  return new Promise((resolve, reject) => {
    // 获取事务对象
    let transaction = db.transaction([storeName]);
    // 获取仓库对象
    let objectStore = transaction.objectStore(storeName);
    // 通过索引查询数据拿到请求对象
    let request = objectStore.index(indexName).get(indexValue);

    request.onerror = function (event) {
      console.log("索引查询失败");
    };
    request.onsuccess = function (event) {
      let result = event.target.result;
      // 返回结果
      resolve(result);
    };
  });
}

索引和游标结合高效查询数据

js 复制代码
/**
 * 通过索引只能查询到符合条件的第一条数据,通过游标只能遍历所有再筛选符合条件的数据!
 * 但是!!!将索引和游标结合可以更高效的获取到符合条件的所有数据
 *
 * IDBKeyRange接口介绍
 * IndexedDB API 的 IDBKeyRange 接口表示一些数据类型上的键的连续间隔。
 * 可以使用一个键或某个范围的键从IDBObjectStore 和IDBIndex 对象中检索记录。
 * 你也可以指定键的上界和下界来限制范围。例如,你可以遍历值范围 a - z 中的键的所有值。
 * @param {IDBDatabase} db
 * @param {String} storeName
 * @param {String} indexName
 * @param {any} indexValue
 */
function getDataByIndexAndCursor(db, storeName, indexName, indexValue) {
  return new Promise((resolve, reject) => {
    let result = [];
    let transaction = db.transaction([storeName]);
    let objectStore = transaction.objectStore(storeName);
    // 获取索引对象
    let index = objectStore.index(indexName);
    // 特定值的游标并获取其请求对象
    let request = index.openCursor(IDBKeyRange.only(indexValue));

    request.onerror = function (event) {
      console.log("请求失败");
    };
    request.onsuccess = function (event) {
      // 获取游标处的数据,返回的是 IDBCursorWithValue 对象,可通过.value拿到其值
      let cursor = event.target.result;
      if (cursor) {
        result.push(cursor.value);
        cursor.continue();
      } else {
        resolve(result);
      }
    };
  });
}

索引和游标结合分页查询数据

js 复制代码
/**
 * indexDB本身不支持分页查询,但是其提供了IDBCursor.advance接口
 * 可以设置光标向前移动的次数,因此基于这个接口可以自己封装分页查询
 * @param {IDBDatabase} db 数据库对象
 * @param {String} storeName 仓库名称
 * @param {String} indexName 索引名
 * @param {String} indexValue 索引值
 * @param {Number} page // 指定开始查询的页数
 * @param {Number} pageSize // 分页大小
 */
function pagingQueryByIndexAndCursor(
  db,
  storeName,
  indexName,
  indexValue,
  page,
  pageSize
) {
  return new Promise((resolve, reject) => {
    // 计数器--统计目标页中遍历了多少数据
    let counter = 0;
    let isAdvance = true;
    let result = []; // 存储结果
    // 获取事务对象
    let transaction = db.transaction([storeName]);
    // 获取仓库对象
    let objectStore = transaction.objectStore(storeName);
    // 指定索引并获取游标请求对象
    let request = objectStore
      .index(indexName)
      .openCursor(IDBKeyRange.only(indexValue));

    request.onerror = function (event) {
      console.log("分页查询失败");
    };
    request.onsuccess = function (event) {
      // 拿到游标指向处的数据对象
      let cursor = event.target.result;
      // 判断是否需要跳过游标
      if (page > 1 && isAdvance) {
        // 指定查询页数大于1时需要跳过
        isAdvance = false;
        cursor && cursor.advance((page - 1) * pageSize);
        return;
      }

      // 检测数据
      if (cursor) {
        result.push(cursor.value);
        // 计数器自增
        counter++;
        // 判断当前页是否遍历完毕
        if (counter < pageSize) {
          // 继续遍历
          cursor.continue();
        } else {
          resolve(result);
        }
      }
      // 排除当分页大小过大,实际检测到的数据太少时,最终结果没有返回
      if (counter < pageSize && !cursor) {
        resolve(result);
      }
    };
  });
}

关闭数据库

js 复制代码
/**
 * 关闭数据库
 * @param {IDBDatabase} db
 */
function closeDB(db) {
  db.close();
  console.log("数据库关闭成功");
}

indexedDB.js

是上述代码的汇总,方便快速投入使用

js 复制代码
/**
 * 打开数据库,没有该数据库则创建
 * @param {String} dbName 数据库名
 * @param {Object} storeInfo 仓库信息,是一个对象,包含name和keyPath属性
 * @param {Number} version
 */
function openDB(dbName, storeInfo, version = 1) {
  return new Promise((resolve, reject) => {
    // 兼容浏览器
    let indexedDB =
      window.indexedDB ||
      window.mozIndexedDB ||
      window.webkitIndexedDB ||
      window.msIndexedDB;
    // 存储数据库对象
    let db;
    // 打开指定名称数据库,没有则创建
    const request = indexedDB.open(dbName, version);
    // 成功的回调
    request.onsuccess = function (event) {
      db = event.target.result; // 数据库对象
      console.log(`数据库${dbName}打开成功`);
      // 返回数据库对象
      resolve(db);
    };
    // 失败的回调
    request.onerror = function (event) {
      console.log(`数据库${dbName}打开失败`);
    };
    // 数据库更新的回调 -- 在数据库创建或升级(版本号升高)时触发
    request.onupgradeneeded = function (event) {
      console.log("数据库创建或升级,已触发onupgradeneeded");
      db = event.target.result; // 保存数据库对象
      let objectStore = db.createObjectStore(storeInfo.name, {
        keyPath: storeInfo.keyPath, // 主键
      });
      // 创建索引,indexedDB查询很不方便,因此常用的可以创建索引便于使用
      objectStore.createIndex("filesTree", "filesTree", { unique: false });
    };
  });
}

/**
 * 数据库插入数据
 * @param {IDBDatabase} db 数据库对象
 * @param {String} storeName 仓库名字
 * @param {Object} data 插入的数据,是一个对象,必须包含创建数据库索引时的那几个属性
 */
function addData(db, storeName, data) {
  // 获取事务对象 -- 由于是添加数据操作,因此事务操作模式为读写
  let transaction = db.transaction([storeName], "readwrite");
  // 获取仓库对象
  let objectStore = transaction.objectStore(storeName);
  // 进行添加操作拿到请求对象
  let request = objectStore.add(data);

  /*上述步骤熟悉后可简化为链式调用
    let request = db
      .transaction([storeName], "readwrite")
      .objectStore(storeName)
      .add(data);
    */

  request.onsuccess = function (event) {
    console.log(`数据${data}插入成功`);
  };
  request.onerror = function (event) {
    console.log(`数据${data}插入失败`, event);
  };
}

/**
 * 数据库的更新数据和插入数据没多大区别,就是add方法变为了put方法
 * put方法在数据库中有这条数据时其作用是修改
 * 但是如果数据库中没有这条数据,则它可以向里面添加数据
 * @param {IDBDatabase} db
 * @param {String} storeName
 * @param {Object} data
 */
function updateData(db, storeName, data) {
  // 获取事务对象 -- 由于是更新,因此操作模式得是读写
  let transaction = db.transaction([storeName], "readwrite");
  // 获取仓库对象
  let objectStore = transaction.objectStore(storeName);
  // 进行数据更新操作并接受请求对象
  let request = objectStore.put(data);

  request.onsuccess = function (event) {
    console.log(`数据${data}更新成功`);
  };
  request.onerror = function (event) {
    console.log(`数据${data}更新失败`, event);
  };
}

/**
 * 通过主键删除数据
 * @param {IDBDatabase} db
 * @param {String} storeName
 * @param {*} keyPathValue
 */
function deleteByKeyPathValue(db, storeName, keyPathValue) {
  // 链式调用 -- 删除操作也改变了数据,因此事务对象操作模式得是读写
  let request = db
    .transaction([storeName], "readwrite")
    .objectStore(storeName)
    .delete(keyPathValue);
  request.onerror = function (event) {
    console.log("删除失败");
  };
  request.onsuccess = function (event) {
    console.log("删除成功");
  };
}

/**
 * 通过索引和游标结合删除数据,本质是利用了游标的cursor.delete()方法删除当前项
 * @param {IDBDatabase} db
 * @param {String} storeName
 * @param {String} indexName
 * @param {any} indexValue
 */
function deleteByIndexAndCursor(db, storeName, indexName, indexValue) {
  // 拿到事务对象
  let transaction = db.transaction([storeName], "readwrite");
  // 拿到仓库对象
  let objectStore = transaction.objectStore(storeName);
  // 获取指定索引的游标请求对象
  let request = objectStore
    .index(indexName)
    .openCursor(IDBKeyRange.only(indexValue));

  request.onerror = function (event) {
    console.log("通过索引游标结合删除失败");
  };
  request.onsuccess = function (event) {
    // 获取游标对象
    let cursor = event.target.result;
    if (cursor) {
      // 删除当前项
      cursor.delete();
      // 游标指向下一项
      cursor.continue();
    } else {
      console.log("删除完毕");
    }
  };
}

/**
 * 通过主键查询数据(异步操作)
 * @param {IDBDatabase} db 数据库对象
 * @param {String} storeName 仓库名字
 * @param {String} key 数据库主键
 */
function getDataByKey(db, storeName, key) {
  return new Promise((resolve, reject) => {
    // 获取事务对象
    let transaction = db.transaction([storeName]);
    // 获取仓库对象
    let objectStore = transaction.objectStore(storeName);
    let request = objectStore.get(key); // 通过主键获取数据
    // 失败回调
    request.onerror = function (event) {
      console.log("数据读取失败");
    };
    // 成功回调
    request.onsuccess = function (event) {
      // 返回结果
      resolve(request.result);
    };
  });
}

/**
 * 通过游标读取数据相当于遍历该仓库,可以在遍历时进行筛选
 * 如果需要直接遍历所有数据的话可以通过getAll方法实现
 * @param {IDBDatabase} db
 * @param {String} storeName
 */
function getDataByCursor(db, storeName) {
  return new Promise((resolve, reject) => {
    let result = [];
    // 获取事务对象
    let transaction = db.transaction([storeName]);
    // 获取仓库对象
    let objectStore = transaction.objectStore(storeName);
    // 打开游标
    let request = objectStore.openCursor();

    // 游标打开成功的回调
    request.onsuccess = function (event) {
      // 获取游标处的数据,返回的是 IDBCursorWithValue 对象,可通过.value拿到其值
      let cursor = event.target.result;
      // 判断该游标指向的对象是否存在
      if (cursor) {
        // 存储值
        result.push(cursor.value);
        // 控制游标指向下一处
        cursor.continue();
      } else {
        resolve(result);
      }
    };
  });
}

/**
 * 通过索引查询数据,注意该方法只能查询出满足条件的第一条数据!
 * 且确保你已经在 request.onupgradeneeded 中创建了索引!
 * @param {IDBDatabase} db
 * @param {String} storeName
 * @param {String} indexName
 * @param {any} indexValue
 */
function getDataByIndex(db, storeName, indexName, indexValue) {
  return new Promise((resolve, reject) => {
    // 获取事务对象
    let transaction = db.transaction([storeName]);
    // 获取仓库对象
    let objectStore = transaction.objectStore(storeName);
    // 通过索引查询数据拿到请求对象
    let request = objectStore.index(indexName).get(indexValue);

    request.onerror = function (event) {
      console.log("索引查询失败");
    };
    request.onsuccess = function (event) {
      let result = event.target.result;
      // 返回结果
      resolve(result);
    };
  });
}

/**
 * 通过索引只能查询到符合条件的第一条数据,通过游标只能遍历所有再筛选符合条件的数据!
 * 但是!!!将索引和游标结合可以更高效的获取到符合条件的所有数据
 *
 * IDBKeyRange接口介绍
 * IndexedDB API 的 IDBKeyRange 接口表示一些数据类型上的键的连续间隔。
 * 可以使用一个键或某个范围的键从IDBObjectStore 和IDBIndex 对象中检索记录。
 * 你也可以指定键的上界和下界来限制范围。例如,你可以遍历值范围 a - z 中的键的所有值。
 * @param {IDBDatabase} db
 * @param {String} storeName
 * @param {String} indexName
 * @param {any} indexValue
 */
function getDataByIndexAndCursor(db, storeName, indexName, indexValue) {
  return new Promise((resolve, reject) => {
    let result = [];
    let transaction = db.transaction([storeName]);
    let objectStore = transaction.objectStore(storeName);
    // 获取索引对象
    let index = objectStore.index(indexName);
    // 特定值的游标并获取其请求对象
    let request = index.openCursor(IDBKeyRange.only(indexValue));

    request.onerror = function (event) {
      console.log("请求失败");
    };
    request.onsuccess = function (event) {
      // 获取游标处的数据,返回的是 IDBCursorWithValue 对象,可通过.value拿到其值
      let cursor = event.target.result;
      if (cursor) {
        result.push(cursor.value);
        cursor.continue();
      } else {
        resolve(result);
      }
    };
  });
}

/**
 * indexDB本身不支持分页查询,但是其提供了IDBCursor.advance接口
 * 可以设置光标向前移动的次数,因此基于这个接口可以自己封装分页查询
 * @param {IDBDatabase} db 数据库对象
 * @param {String} storeName 仓库名称
 * @param {String} indexName 索引名
 * @param {String} indexValue 索引值
 * @param {Number} page // 指定开始查询的页数
 * @param {Number} pageSize // 分页大小
 */
function pagingQueryByIndexAndCursor(
  db,
  storeName,
  indexName,
  indexValue,
  page,
  pageSize
) {
  return new Promise((resolve, reject) => {
    // 计数器--统计目标页中遍历了多少数据
    let counter = 0;
    let isAdvance = true;
    let result = []; // 存储结果
    // 获取事务对象
    let transaction = db.transaction([storeName]);
    // 获取仓库对象
    let objectStore = transaction.objectStore(storeName);
    // 指定索引并获取游标请求对象
    let request = objectStore
      .index(indexName)
      .openCursor(IDBKeyRange.only(indexValue));

    request.onerror = function (event) {
      console.log("分页查询失败");
    };
    request.onsuccess = function (event) {
      // 拿到游标指向处的数据对象
      let cursor = event.target.result;
      // 判断是否需要跳过游标
      if (page > 1 && isAdvance) {
        // 指定查询页数大于1时需要跳过
        isAdvance = false;
        cursor && cursor.advance((page - 1) * pageSize);
        return;
      }

      // 检测数据
      if (cursor) {
        result.push(cursor.value);
        // 计数器自增
        counter++;
        // 判断当前页是否遍历完毕
        if (counter < pageSize) {
          // 继续遍历
          cursor.continue();
        } else {
          resolve(result);
        }
      }
      // 排除当分页大小过大,实际检测到的数据太少时,最终结果没有返回
      if (counter < pageSize && !cursor) {
        resolve(result);
      }
    };
  });
}

/**
 * 关闭数据库
 * @param {IDBDatabase} db
 */
function closeDB(db) {
  db.close();
  console.log("数据库关闭成功");
}
相关推荐
极小狐18 分钟前
极狐GitLab 容器镜像仓库功能介绍
java·前端·数据库·npm·gitlab
程序猿阿伟30 分钟前
《Flutter社交应用暗黑奥秘:模式适配与色彩的艺术》
前端·flutter
rafael(一只小鱼)34 分钟前
黑马点评实战笔记
前端·firefox
weifont34 分钟前
React中的useSyncExternalStore使用
前端·javascript·react.js
初遇你时动了情39 分钟前
js fetch流式请求 AI动态生成文本,实现逐字生成渲染效果
前端·javascript·react.js
影子信息1 小时前
css 点击后改变样式
前端·css
几何心凉1 小时前
如何使用 React Hooks 替代类组件的生命周期方法?
前端·javascript·react.js
小堃学编程1 小时前
前端学习(1)—— 使用HTML编写一个简单的个人简历展示页面
前端·javascript·html
hnlucky2 小时前
通俗易懂版知识点:Keepalived + LVS + Web + NFS 高可用集群到底是干什么的?
linux·前端·学习·github·web·可用性测试·lvs
懒羊羊我小弟3 小时前
使用 ECharts GL 实现交互式 3D 饼图:技术解析与实践
前端·vue.js·3d·前端框架·echarts