IndexedDB教程

一、概述

IndexedDB 是浏览器提供的本地数据库,js原生支持创建和操作 IndexedDB。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。就数据库类型而言,IndexedDB 不属于关系型数据库,更接近 NoSQL 数据库(存储key-value)。

它具有如下特点:

  • 键值对储存
  • 异步。IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作。
  • 支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
  • 同源限制。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
  • 存储空间大。在默认使用短暂存储情况下,一组域名受到组限制,共享最小10MB,最大2GB的存储空间。
  • 支持二进制存储。如 ArrayBuffer 对象和 Blob 对象

二、基本概念

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

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

下面是一些主要的概念。

(1) 数据库

数据库是一系列相关数据的容器。每个域名(严格的说,是协议 + 域名 + 端口)都可以新建任意多个数据库。

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

(2) 对象仓库

每个数据库包含若干个对象仓库(object store)。它类似于关系型数据库的表。

(3) 数据记录

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

{ id: 1, text: 'foo' }

上面是一个object store 中的一条数据,我们可以指定id属性作为该object store的主键,这条数据的数据体就是{ id: 1, text: 'foo' }

(4) 索引

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

(5) 事务

数据的增删改查、数据库版本的升级都要通过事务完成,总共有三种事务模式:readwritereadonlyversionchange

三、API 介绍

下面通过具体操作数据库时的流程,介绍相关 API。

3.1 打开数据库

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

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

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

indexedDB.open()方法返回一个 IDBRequest 对象。这个对象通过三种事件errorsuccessupgradeneeded,处理打开数据库的操作结果。errorsuccess事件表示打开数据库失败、成功,如果数据库不存在或者指定打开的版本大于实际的数据库版本,就会触发数据库升级事件upgradeneeded。这时通过事件对象的target.result属性,拿到数据库实例。

var db;request.onsuccess = function (event) {db = request.result;console.log('数据库打开成功');
};request.onerror = function (event) {console.log('数据库打开报错');
};request.onupgradeneeded = function (event) {db = event.target.result;
}

注意:版本号是 unsigned long long 类型,不是浮点型,不能使用 2.4 作为版本号。

3.2 新建数据库

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

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

request.onupgradeneeded = function(event) {db = event.target.result;var objectStore = db.createObjectStore('person', { keyPath: 'id' });
}

上面代码中,数据库新建成功以后,新增一张叫做person的表格,主键是id。如果数据记录里面没有合适作为主键的属性,那么可以让 IndexedDB 自动生成主键。

var objectStore = db.createObjectStore('person',{ autoIncrement: true }
);

上面代码中,指定主键为一个递增的整数。

新建对象仓库以后,下一步可以新建索引。

request.onupgradeneeded = function(event) {db = event.target.result;var objectStore = db.createObjectStore('person', { keyPath: 'id' });objectStore.createIndex('name', 'name', { unique: false });objectStore.createIndex('email', 'email', { unique: true });
}

上面代码中,IDBObject.createIndex()的三个参数分别为索引名称、索引所在的属性、配置对象(unique属性表示是否包含重复的值)。

3.3 数据操作

这里只介绍新增数据,其余操作与此类似,具体使用参考下一章节在项目中封装使用
新增数据指的是向对象仓库写入数据记录。这需要通过事务完成。

function add() {var request = db.transaction('person', 'readwrite').objectStore('person').add({ id: 1, name: '张三', age: 24, email: 'zhangsan@example.com' });request.onsuccess = function (event) {console.log('数据写入成功');};request.onerror = function (event) {console.log('数据写入失败');}
}add();

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

写入操作是一个异步操作,通过监听连接对象的success事件和error事件,了解是否写入成功。

3.4 使用索引

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

假定新建表格的时候,对name字段建立了索引。

objectStore.createIndex('name', 'name', { unique: false });

现在,就可以从name找到对应的数据记录了。

var transaction = db.transaction('person', 'readonly');
var store = transaction.objectStore('person');
var index = store.index('name');
var request = index.get('李四');request.onsuccess = function (e) {var result = e.target.result;if (result) {// ...} else {// ...}
}

四、在项目中封装使用

4.1 打开(新建)数据库,DBInstance.ts: openDatabase

使用数据库的第一步就是打开或新建一个数据库

4.1.1 实例

创建一个学生数据库,其中有两个object store。一个名为studentInfoobject store记录学生的身份信息,以学生的id属性作为主键,同时对agename字段建立索引。另一个名为scoreobject store记录学生的分数信息,自动递增生成主键。

enum StoreName {Student = 'studentInfo',Score = 'score'
}
const studentSchema: Array<DBStoreType> = [{dbStore: {dbStoreName: StoreName.Student,},dbIndex: [{dbIndexName: 'age',keyPath: 'age',},{dbIndexName: 'name',keyPath: 'name'}]},{dbStore: {dbStoreName: StoreName.Score,options: {autoIncrement: true}}}
]
const studentDatabase = await openDatabase('student', 1, studentSchema);

4.1.2 定义和用法

下面是openDatabase函数的函数体。

openDatabase函数返回一个 Promise 对象,可异步获取打开的数据库对象实例。如果打开的对象不存在或者数据库版本号比实际版本大,将会触发upgradeneeded事件,在该事件中,首先删除旧有版本的所有object store,然后建立新版本的数据库。

4.1.3 参数说明

  • dbName:string

数据库名。

  • dbVersion:number

数据库版本号。

  • dbStores:Array<DBStoreType>

数据库信息,可以有多个object store,单个object store可声明多个索引。

type DBStoreType = {dbStore: DBStoreParameter;dbIndexs?: Array<DBIndexParameter>;
}
type DBStoreParameter = {dbStoreName: string;options?: IDBObjectStoreParameters;
}
type DBIndexParameter = {dbIndexName: string;keyPath: string | string[];options?: IDBIndexParameters;
}

上面代码中,可以看到DBStoreTypedbStoredbIndexs属性组成。

4.2 Object Store 封装类,DBObjectStore.ts: DBObjectStore

4.2.1 实例

实例化DBObjectStore,对studentDatabase中的student表进行各种数据操作,包括增删改查,迭代。

type StudentInfo = {id: number;name: string;age: number;
}
const StudentStore = DBObjectStore<[number],StudentInfo>


4.2.2 定义和用法

DBObjectStore 类初始化时,获得了数据库对象实例database以及要操作的object store名。

DBObjectStore是一个泛型类,需要传递类型作为参数,以约束object store中存储数据的keyvalue的类型。

注意:当不设置主键时,使用默认递增的主键,它的类型为 number。设置主键后,value 的类型必须为 object 类型,不能是 number, string 等类型。

4.2.3 DBObjectStore 方法介绍

  • getStore(storeName: string, mode? IDBTransactionMode)

返回 object store,准备开始操作数据,数据操作在指定模式下的事务中进行。不知道事务模式,默认为 readonly。

  • put(value: V, key? K): Promise<Event>

增加或修改数据,返回一个 Promise 对象,增加或修改成功传递 Event 事件参数回调。

增加还是修改取决于object store中是否含有该键指向的数据。

如果设置了指定字段作为主键,那么就不需要使用第二个参数,value 中已包含该主键值。未指定主键,按默认主键,如果设置为主键递增增加,则第二参数可要可不要。

  • putBulk(value: Array<V>): Promise<Event>

批量一次性添加多条数据。返回一个 Promise 对象,成功则传递事件参数 Event 回调。

  • delete(query: K | IDBKeyRange): Promise<Event>

删除一条数据或多条数据。返回一个 Promise 对象,成功则传递事件参数 Event 回调。

  • get(query: K): Promise<V>

按主键查询一条数据。返回一个 Promise 对象,成功则传递查询结果回调。

  • getRange(query: IDBKeyRange): Promise<Array<V>>

按主键范围返回查询数据。返回一个 Promise 对象,成功则传递查询结果回调。

  • getAll(): Promise<Array<V>>

查询当前object store的所有数据。返回一个 Promise 对象,成功则传递查询结果回调。

  • getByIndex(indexName: string, key: IDBValidKey): Promise<V>

按索引中的 key 查询一条数据。返回一个 Promise 对象,成功则传递查询结果回调。

  • getRangeByIndex(indexName: string, key: IDBKeyRange): Promise<Array<V>>

按索引中的 key范围查询多条数据。返回一个 Promise 对象,成功则传递查询结果回调。

  • iterate(iterateCall: (value: V) => void,query?:K | IDBValidKey | IDBKeyRange | null, indexName?: string, direction?: IDBCursorDirection): Promise<Array<V>>

迭代object store或索引,可指定迭代范围,不指定迭代所有,也可指定迭代方向。迭代操作,通过传入的iterateCall完成

  • clearData(): Promise<Event>

清空当前object store中的数据。返回一个 Promise 对象,成功则传递事件参数 Event 回调。

五、数据库安全与管理

5.1 安全

IndexedDB遵守同源原则,这意味着某个源创建的数据库只能在该源里访问。但是也有特殊情况,下面这句话来自mdn文档:

Third party window content (e.g. <iframe> content) cannot access IndexedDB if the browser is set to never accept third party cookies (see bug 1147821.)

这段英文不太能理解,另外我又在微软的技术文档里找到了另一种表述。

经过实验,发现是页面能访问 <iframe> 中的 indexedDB,但是<iframe>无法访问页面的 indexedDB。

5.2 数据库管理

indexedDB 创建的数据库内容始终是保留在用户端的,每当我们想改变数据库的结构,就要考虑用户端本地原来保存的是什么样的数据库。
最简单的方法,就是在数据库版本提升时,清空旧的 object store。这样当用户再次访问我们网站时,就会执行这段脚本,清空旧数据。

当某个数据库弃用时也需要,写一段脚本去删除。

(window as any)._indexedDB.deleteDatabase('databaseName');

数据库结构对应 openDatabase 函数的 dbStores:Array<DBStoreType> 参数,当其改变时,提升打开的数据库版本号以触发 onupgradeneeded 事件。

参考链接

  • 浏览器数据库 IndexedDB 入门教程,阮一峰,https://www.ruanyifeng.com/blog/2018/07/indexeddb.html
  • IndexedDB API,mdn web docs,https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API

IndexedDB教程相关推荐

  1. 前端每周清单第 33 期:React 16 发布与特性介绍,Expo AR 教程,ExtJS 从崛起到沉寂...

    前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点:分为新闻热点.开发教程.工程实践.深度阅读.开源项目.巅峰人生等栏目.欢迎关注[前端之巅]微信公众号(ID:front ...

  2. 《HTML5与CSS3实例教程》

    <HTML5与CSS3实例教程> 基本信息 作者: (美)Brian P. Hogan 译者: 卢俊祥 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:97871153634 ...

  3. 一行代码,搞定浏览器数据库 IndexedDB

    作者 | 星尘starx 来源 | https://juejin.cn/post/6918705632757415950 前言 2021 年,如果你的前端应用,需要在浏览器上保存数据,有三个主流方案可 ...

  4. html5实践开发教程,HTML5基础与实践教程

    HTML5基础与实践教程 语音 编辑 锁定 讨论 上传视频 <HTML5基础与实践教程>是2010年4月机械工业出版社出版的图书,作者是云翔,刘猛猛,欧阳植昊. 书    名 HTML5基 ...

  5. 本教程针对HBuilder5.0.0,制作日期2014-12-31(从HBuilder工具上获得)

     /*注:本教程针对HBuilder5.0.0,制作日期2014-12-31*/ 创建HTML结构: h 8 (敲h激活代码块列表,按8选择第8个项目,即HTML代码块,或者敲h t Enter) ...

  6. 最实用的18个HTML5 API 教程大全,都在这里了

    HTML5是web前端开发者必备的能力之一,公众号之前已经给同学们带来很多相关的教程,现在小编在这里将所有的API教程进行一次汇总,方便同学们学习. API详解1:实现fullscreen全屏模式 A ...

  7. cookie可存的最大限制_一文梳理Web存储,从cookie,WebStorage到IndexedDB

    前言 HTTP是无状态的协议,网络早期最大的问题之一是如何管理状态.服务器无法知道两个请求是否来自同一个浏览器.cookie应运而生,开始出现在各大网站,然而随着前端应用复杂度的提高,Cookie 也 ...

  8. Chrome浏览器及调试教程

    ==>(微信公众号:IT知更鸟)欢迎关注<^>@<^> Chrome浏览器及调试教程 在web开发过程中,我们在写JavaScript脚本时难免会遇到各种bug,这时,我 ...

  9. Hbuilder快捷键教程

    /*注:本教程针对HBuilder5.0.0,制作日期2014-12-31*/ 创建HTML结构: h 8 (敲h激活代码块列表,按8选择第8个项目,即HTML代码块,或者敲h t Enter) 中途 ...

最新文章

  1. vs中.exe运行闪退的解决办法
  2. token验证_java基于token的身份验证?读完之后,大部分程序员收藏了...
  3. Hibernate- 包作用详解
  4. 12v电流表的正确接法_难点分析 | 电表的内外接法
  5. vue项目封装axios
  6. 如何让cloudflare缓存html,CloudFlareCDN页面规则缓存设置教程
  7. linux的定cron计划任务命令
  8. 【PAT乙】1085 PAT单位排行 (25分) map排序
  9. 计算机丢失lame,libmp3lame64.dll
  10. 实验一 网络侦查与网络扫描
  11. Arcgis Engine 面的创建和设置
  12. php取tet文件内容,PHP中使用PDFlib TET提取PDF中的文本
  13. 通过微透镜阵列的传播
  14. 常用资源环境生态地理空间数据开源下载地址整理
  15. protues用一片74hc595控制两位数码管
  16. 物联网技术概论:1~7章汇总(西安交通大学)
  17. Github 镜像站的使用
  18. win10 安装 ros2.0---ROS Bouncy
  19. 【苹果家庭共享软件】设备安装iOS13增加了iMessage群发紧缩格式
  20. 变电站电源屏及温湿度和烟感设备协议接入流程记录

热门文章

  1. php 怎么提价redius,调价or涨价的英文怎么说?
  2. js判断是否是合法数字方法
  3. 关于逆元(费马小定理,exgcd)
  4. 聊聊高大上的物联网(智能家居)技术及平台
  5. 用户为什么收不到彩信手机报
  6. kotlin设置按钮不可点击_电脑护眼设置开启教程
  7. BootStrap_03(表单、按钮、图片、辅助类)
  8. 固件解包--binwalk分析
  9. window+kill+mysql慢查询_MySQL优化-(2)-慢查询日志工具-pt-query-digest
  10. 2022最新微信小程序授权登录(前后端分离)