首页 > 其他分享 >IndexedDB

IndexedDB

时间:2024-11-28 11:34:46浏览次数:8  
标签:IndexedDB string storeName db 数据库 request 索引

IndexedDB简介

MDN官网是这样解释Indexed DB的:

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

通俗地说,IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。

客户端各存储方式对比:

IndexedDB使用场景

所有的场景都基于客户端需要存储大量数据的前提下:

  1. 数据可视化等界面,大量数据,每次请求会消耗很大性能。

  2. 即时聊天工具,大量消息需要存在本地。

  3. 其它存储方式容量不满足时,不得已使用IndexedDB。

IndexedDB特点

(1) 非关系型数据库(NoSql)

我们都知道MySQL等数据库都是关系型数据库,它们的主要特点就是数据都以一张二维表的形式存储,而Indexed DB是非关系型数据库,主要以键值对的形式存储数据。

(2)持久化存储

cookie、localStorage、sessionStorage等方式存储的数据当我们清楚浏览器缓存后,这些数据都会被清除掉的,而使用IndexedDB存储的数据则不会,除非手动删除该数据库。

(3)异步操作

IndexedDB操作时不会锁死浏览器,用户依然可以进行其他的操作,这与localstorage形成鲜明的对比,后者是同步的。

(4)支持事务

IndexedDB支持事务(transaction),这意味着一系列的操作步骤之中,只要有一步失败了,整个事务都会取消,数据库回滚的事务发生之前的状态,这和MySQL等数据库的事务类似。

(6)同源策略

IndexedDB同样存在同源限制,每个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。

(7)存储容量大

这也是IndexedDB最显著的特点之一了,这也是不用localStorage等存储方式的最好理由。

IndexedDB重要概念讲解

仓库objectStore

IndexedDB没有表的概念,它只有仓库store的概念,大家可以把仓库理解为表即可,即一个store是一张表。

索引index

在关系型数据库当中也有索引的概念,我们可以给对应的表字段添加索引,以便加快查找速率。

游标cursor

游标是IndexedDB数据库新的概念,大家可以把游标想象为一个指针,比如我们要查询满足某一条件的所有数据时,就需要用到游标,我们让游标一行一行的往下走,游标走到的地方便会返回这一行数据,此时我们便可对此行数据进行判断,是否满足条件。

【注意】:IndexedDB查询不像MySQL等数据库方便,它只能通过主键、索引、游标方式查询数据。

事务

数据记录的读写和删改,都要通过事务完成。即对数据库进行操作时,只要失败了,都会回滚到最初始的状态,确保数据的一致性。

IndexedDB实操

IndexedDB所有针对仓库的操作都是基于事务的。

新建/打开数据库

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

var request = window.indexedDB.open(databaseName, version);

这个方法接受两个参数,第一个参数是字符串,表示数据库的名字。如果指定的数据库不存在,就会新建数据库。第二个参数是整数,表示数据库的版本。如果省略,打开已有数据库时,默认为当前版本;新建数据库时,默认为1。

indexedDB.open()方法返回一个 IDBRequest 对象。这个对象通过三种事件errorsuccessupgradeneeded,处理打开数据库的操作结果。

(1)error 事件

error事件表示打开数据库失败。

request.onerror = (event) => {
  console.log('数据库打开报错');
};

(2)success 事件

success事件表示成功打开数据库。

let db;
request.onsuccess = (event) => {
  db = request.result;
  console.log('数据库打开成功');
};

这时,通过request对象的result属性拿到数据库对象。

(3)upgradeneeded 事件

当数据库版本有变化的时候会执行该函数,比如我们想创建新的存储库(表),就可以在该函数里面操作,更新数据库版本即可。

let db;
request.onupgradeneeded = (event) => {
  db = event.target.result;
}

这时通过事件对象的target.result属性,拿到数据库实例。

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

通常,新建数据库以后,第一件事是新建对象仓库(即新建表)。

request.onupgradeneeded = function (event: any) {
  // 数据库创建或升级的时候会触发
  console.log("onupgradeneeded");
  db = event.target.result; // 数据库对象
  var objectStore;
  // 新建对象仓库(即新建表)
  objectStore = db.createObjectStore("users", {
    keyPath: "uid", // 主键
    // autoIncrement: true // 如果数据记录里面没有合适作为主键的属性,那么可以让 IndexedDB 自动生成主键
  });
};

主键(key)是默认建立索引的属性。

通过IDBObject.createIndex()新建索引。

IDBObject.createIndex()的三个参数分别为索引名称、索引所在的属性、配置对象(说明该属性是否包含重复的值)。

request.onupgradeneeded = function (event: any) {
  // 数据库创建或升级的时候会触发
  console.log("onupgradeneeded");
  db = event.target.result; // 数据库对象
  var objectStore;
  // 新建对象仓库(即新建表)
  objectStore = db.createObjectStore("users", {
    keyPath: "uid", // 主键
    // autoIncrement: true // 如果数据记录里面没有合适作为主键的属性,那么可以让 IndexedDB 自动生成主键
  });
  // 创建索引,在后面查询数据的时候可以根据索引查
  objectStore.createIndex("name", "name", { unique: true });
  objectStore.createIndex("name", "name", { unique: false });
  objectStore.createIndex("age", "age", { unique: false });
};

新增数据

新增数据指的是向对象仓库写入数据记录。这需要通过事务完成。

/**
 * 新增数据
 * @param {object} db 数据库实例
 * @param {string} storeName 仓库名称
 * @param {string} data 数据
 */
  addData(db: any, storeName: string, data: any) {
    return new Promise<void>((resolve, reject) => {
      const request = db
        .transaction([storeName], "readwrite") // 事务对象 指定表格名称和操作模式("只读"或"读写")
        .objectStore(storeName) // 仓库对象
        .add(data);
      request.onsuccess = function () {
        console.log("数据写入成功");
        resolve()
      };
      request.onerror = function () {
        console.log("数据写入失败");
        reject()
      };
    })
  }

上面代码中,写入数据需要新建一个事务。新建时必须指定表格名称和操作模式("只读"或"读写")。新建事务以后,通过IDBTransaction.objectStore(name)方法,拿到 IDBObjectStore 对象,再通过表格对象的add()方法,向表格写入一条记录。

add()接收三个参数,分别如下:

  • db:在创建或连接数据库时,返回的db实例,需要那个时候保存下来。

  • storeName:仓库名称(或者表名),在创建或连接数据库时我们就已经创建好了仓库。

  • data:需要插入的数据,通常是一个对象。

通过主键读取数据

读取数据也是通过事务完成。

/**
 * 通过主键读取数据
 * @param {object} db 数据库实例
 * @param {string} storeName 仓库名称
 * @param {string} key 主键值
 */
getDataByKey(db: any, storeName: string, key: number) {
    return new Promise((resolve, reject) => {
      const transaction = db.transaction([storeName]); // 事务
      const objectStore = transaction.objectStore(storeName); // 仓库对象
      const request = objectStore.get(key); // 通过主键获取数据
      request.onerror = () => {
        console.log("事务失败");
        reject();
      };
      request.onsuccess = () => {
        console.log("主键查询结果: ", request.result);
        resolve(request.result);
      };
    });
  }

上面代码中,objectStore.get()方法用于读取数据,参数是主键的值。

通过游标查询数据

/**
 * 通过游标读取数据
 * @param {object} db 数据库实例
 * @param {string} storeName 仓库名称
 */
cursorGetData(db: any, storeName: string) {
    return new Promise((resolve, reject) => {
      const list:any[] = [];
      const store = db
        .transaction(storeName, "readwrite") // 事务
        .objectStore(storeName); // 仓库对象
      const request = store.openCursor(); // 指针对象
      // 游标开启成功,逐行读数据
      request.onsuccess = (e: any) => {
        const cursor = e.target.result;
        if (cursor) {
          // 必须要检查
          list.push(cursor.value);
          cursor.continue(); // 遍历了存储对象中的所有内容
        } else {
          console.log("游标读取的数据:", list);
          resolve(list);
        }
      };
      request.onerror = () => {
        console.log("游标读取失败");
        reject();
      }
    })
  }

上面函数开启了一个游标,然后逐行读取数据,存入数组,最终得到整个仓库的所有数据。

通过索引查询数据

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

/**
 * 通过索引读取数据
 * @param {object} db 数据库实例
 * @param {string} storeName 仓库名称
 * @param {string} indexName 索引名称
 * @param {string} indexValue 索引值
 */
getDataByIndex(db: IDBDatabase, storeName: string, indexName: string, indexValue: number) {
    return new Promise((resolve, reject) => {
      const store = db.transaction(storeName, "readonly").objectStore(storeName);
      const request = store.index(indexName).get(indexValue);
      request.onerror = function () {
        console.log("事务失败");
        reject();
      };
      request.onsuccess = function (e) {
        const result = (e.target as IDBOpenDBRequest).result;
        console.log("索引查询结果:", result);
        resolve(result);
      };
    })
 }

索引名称即我们创建仓库的时候创建的索引名称,也就是键值对中的键,最终会查询出所有满足我们传入函数索引值的数据。

单独通过索引或者游标查询出的数据都是部分或者所有数据,如果我们想要查询出索引中满足某些条件的所有数据,那么单独使用索引或游标是无法实现的。当然,你也可以查询出所有数据之后在循环数组筛选出合适的数据,但是这不是最好的实现方式,最好的方式当然是将索引和游标结合起来。

值得注意的是使用了IDBKeyRange.only()API,该API代表只能当两个值相等时,具体API解释可参考MDN官网。

/**
 * 通过索引和游标查询记录
 * @param {object} db 数据库实例
 * @param {string} storeName 仓库名称
 * @param {string} indexName 索引名称
 * @param {string} indexValue 索引值
 */
cursorGetDataByIndex(db: IDBDatabase, storeName: string, indexName: string, indexValue: number) {
    return new Promise((resolve, reject) => {
      const list: any = [];
      const store = db.transaction(storeName, "readwrite").objectStore(storeName); // 仓库对象
      const request = store
        .index(indexName) // 索引对象
        .openCursor(IDBKeyRange.only(indexValue)); // 指针对象
      request.onsuccess = function (e) {
        const cursor: any = (e.target as IDBOpenDBRequest).result;
        if (cursor) {
          list.push(cursor.value);
          cursor.continue();
        } else {
          console.log("游标索引查询结果:", list);
          resolve(list);
        }
      };
      request.onerror = function (e) {
        reject();
      };
    })
  }

更新数据

IndexedDB更新数据较为简单,直接使用put方法,值得注意的是如果数据库中没有该条数据,则会默认增加该条数据,否则更新。

/**
 * 更新数据
 * @param {object} db 数据库实例
 * @param {string} storeName 仓库名称
 * @param {object} data 数据
 */
updateDB(db: IDBDatabase, storeName: string, data: any): Promise<void> {
    return new Promise((resolve, reject) => {
      const request = db
        .transaction([storeName], "readwrite") // 事务对象
        .objectStore(storeName) // 仓库对象
        .put(data);
    
      request.onsuccess = function () {
        console.log("数据更新成功");
        resolve();
      };
    
      request.onerror = function () {
        console.log("数据更新失败");
        reject();
      };
    })
  }

删除数据

IDBObjectStore.delete()方法用于删除记录。

 /**
 * 通过主键删除数据
 * @param {object} db 数据库实例
 * @param {string} storeName 仓库名称
 * @param {object} id 主键值
 */
 deleteDB(db: IDBDatabase, storeName: string, id: number) {
    return new Promise(() => {
      const request = db
        .transaction([storeName], "readwrite")
        .objectStore(storeName)
        .delete(id); 
      request.onsuccess = function () {
        console.log("数据删除成功");
      };
      request.onerror = function () {
        console.log("数据删除失败");
      };
    })
  }

该种删除只能删除一条数据,必须传入主键。

有时候我们拿不到主键值,只能只能通过索引值来删除,通过这种方式,我们可以删除一条数据(索引值唯一)或者所有满足条件的数据(索引值不唯一)。

/**  
* 通过索引和游标删除指定的数据  
* @param {object} db 数据库实例  
* @param {string} storeName 仓库名称  
* @param {string} indexName 索引名  
* @param {object} indexValue 索引值  
*/
cursorDelete(db: IDBDatabase, storeName: string, indexName: string, indexValue: number | string): Promise<void> {
    return new Promise((resolve, reject) => {
      const store = db.transaction(storeName, "readwrite").objectStore(storeName);
      const request = store
        .index(indexName) // 索引对象
        .openCursor(IDBKeyRange.only(indexValue)); // 指针对象
      request.onsuccess = function (e) {
        const cursor: any  = (e.target as IDBOpenDBRequest).result;
        let deleteRequest;
        if (cursor) {
          deleteRequest = cursor.delete(); // 请求删除当前项
          deleteRequest.onerror = function () {
            console.log("游标删除该记录失败");
            reject();
          };
          deleteRequest.onsuccess = function () {
            console.log("游标删除该记录成功");
            resolve();
          };
          cursor.continue();
        }
      };
      request.onerror = function (e) {
        console.log(e);
        reject();
      };
    })
  }

关闭数据库

当我们数据库操作完毕后,建议关闭它,节约资源。

closeDB(db: IDBDatabase) {
  db.close();
  console.log("数据库已关闭");
}

删除数据库

最后我们需要删库跑路,删除操作也很简单。

deleteDBAll(dbName: string): Promise<void> {
    return new Promise((resolve, reject) => {
      const deleteRequest = window.indexedDB.deleteDatabase(dbName);
      deleteRequest.onerror = function (event) {
        console.log("删除失败");
        reject();
      };
      deleteRequest.onsuccess = function (event) {
        console.log("删除成功");
        resolve();
      };
    })
  }

参考文档

https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API

标签:IndexedDB,string,storeName,db,数据库,request,索引
From: https://www.cnblogs.com/yangser/p/18573953

相关文章

  • 一个简单的 indexedDB 应用示例
    现代浏览器中会包含indexedDB,这是一个功能比localStorage更加强大的数据库引擎,其API描述详见W3规范:IndexedDB如下是一个简单的应用示例:<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"/><metaname="viewport"content=......
  • 浅析浏览器数据库IndexedDB:基本概念、操作流程及各对象属性和方法
    一、概述随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据储存在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。Cookie的大小不超过4KB,且每次请求都会发送回服务器;LocalStorage在2.5MB到10MB之间(各家浏览器不同),而且不提供搜索功能,不能建......
  • JavaScript-IndexedDB API
    概述随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据储存在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。现有的浏览器数据储存方案,都不适合储存大量数据:Cookie的大小不超过4KB,且每次请求都会发送回服务器;LocalStorage在2.5MB到10MB之间(各家浏览器不......
  • IndexedDB设计及封装
    设计思路固定数据表键值对表用于存储数据库相关的信息库字段构成表储存非固定数据表结构非固定数据表通过库字段构成表进行创建或更新划重点数据库初始创建或更新后会先触发onupgradeneeded方法,然后再触发onsuccess方法,如果在onupgradeneeded方法中执行了表结构操作的......
  • 前端新手必读:IndexedDB全面指北
    大家好,我是星辰编程理财,今天介绍IndexedDB。一、引言1.背景和用途IndexedDB是一种在浏览器中使用的客户端数据库,它提供了一种存储和检索大量结构化数据的方式。与传统的Web存储技术(如LocalStorage和Cookies)相比,IndexedDB具有更强大的功能和更高的性能。IndexedDB的背景可以追溯到......
  • HTML5本地存储——IndexedDB
      在HTML5本地存储——WebSQLDatabase提到过WebSQLDatabase实际上已经被废弃,而HTML5的支持的本地存储实际上变成了WebStorage(LocalStorage和SessionStorage)与IndexedDB。WebStorage使用简单字符串键值对在本地存储数据,方便灵活,但是对于大量结构化数据存储力不从心,Indexed......
  • web网站使用indexedDB缓存大数量案例
    前言及背景indexedDB是html5标准引入的web数据持久化方案之一,现代浏览器大多按照标准对其进行了实现,我在新的项目中用到它来作为持久化数据存储,由于最近在web端项目,每次web前端需要实时计算中间成果预计18G的中间过度数据,预计最终每次生成200M以上的结果数据,在此过程耗时30多......
  • 存储IndexedDB之Dexie
    [前端大容量存储IndexedDB之Dexie.js-掘金](https://juejin.cn/post/7025592963002531871) IndexedDB存储Dexie.js 对IndexedDB的封装,语法简单,可以快速方便的编写代码 webSQL总结webSQL标准不再更新,关系型数据库,底层sqlitechrome中容量5M,支持同域名不同页面共享......
  • 第128篇:浏览器存储(cookie、webStorage、 IndexedDB)
    好家伙,本篇为《JS高级程序设计》第二五章“浏览器存储”学习笔记 我们先来讲个故事一个“薅羊毛”的故事(qq.com)概括一下,就是有个人通过网络平台非法购买了大量“c......
  • 浏览器数据库indexeddb-数据库缓存
    随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据储存在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。现有的浏览器数据储存方案,都不适合储存大量数......