indexedDB 基本使用 WebSQL 数据库的取代品
原文出处: 党黎明
indexedDB
简介:
indexedDB
是一种使用浏览器存储大量数据的方法.它创造的数据可以被查询,并且可以离线使用.
indexedDB
有以下特点:
indexedDB
是WebSQL
数据库的取代品indexedDB
遵循同源协议(只能访问同域中存储的数据,而不能访问其他域的)API
包含异步API和同步API两种:多数情况下使用异步API; 同步API必须同 WebWorkers 一起使用, 目前没有浏览器支持同步APIindexedDB
是事务模式的数据库, 使用key-value
键值对储存数据indexedDB
不使用结构化查询语言(SQL
). 它通过索引(index
)所产生的指针(cursor
)来完成查询操作
一、使用indexedDB的基本模式
- 打开数据库并且开始一个事务。
- 创建一个
objecStore
。 - 构建一个请求来执行一些数据库操作,像增加或提取数据等。
- 通过监听正确类型的
DOM
事件以等待操作完成。 - 在操作结果上进行一些操作(可以在
request
对象中找到)
二、创建、打开数据库
indexedDB
存在于全局对象window
上, 它最重要的一个方法就是open
方法, 该方法接收两个参数:
dbName
// 数据库名称[string]
version
// 数据库版本[整型number]
1234567891011121314151617181920 | var DB_NAME = 'indexedDB-test', VERSION = 1, db;var request = indexedDB.open(DB_NAME, VERSION);request.onsuccess = function(event) { db = event.target.result; // console.log(event.target === request); // true db.onsuccess = function(event) { console.log('数据库操作成功!'); }; db.onerror = function(event) { console.error('数据库操作发生错误!', event.target.errorCode); }; console.log('打开数据库成功!');};request.onerror = function(event) { console.error('创建数据库出错'); console.error('error code:', event.target.errorCode);};request.onupgradeneeded = function(event) { // 更新对象存储空间和索引 .... }; |
若是本域下不存在名为DB_NAME
的数据库,则上述代码会创建一个名为DB_NAME
、版本号为VERSION
的数据库; 触发的事件依次为: upgradeneeded
、 success
.
若是已存在名为DB_NAME
的数据库, 则上述代码会打开该数据库; 只触发success
/error
事件,不会触发upgradeneeded
事件. db
是对该数据库的引用.
三、创建对象存储空间和索引
在关系型数据库(如mysql)中,一个数据库中会有多张表,每张表有各自的主键、索引等;
在key-value
型数据库(如indexedDB)中, 一个数据库会有多个对象存储空间,每个存储空间有自己的主键、索引等;
创建对象存储空间的操作一般放在创建数据库成功回调里:
123456 | request.onupgradeneeded = function(event) { // 更新对象存储空间和索引 .... var database = event.target.result; var objectStore = database.createObjectStore("movies", { keyPath: "id" }); objectStore.createIndex('alt', 'alt', { unique: true }); objectStore.createIndex('title', 'title', { unique: false });}; |
onupgradeneeded
是我们唯一可以修改数据库结构的地方。在这里面,我们可以创建和删除对象存储空间以及构建和删除索引。
在数据库对象database
上,有以下方法可供调用:
createObjectStore(storeName, configObj)
创建一个对象存储空间storeName
// 对象存储空间的名称[string]
configObj
// 该对象存储空间的配置[object]
(其中的keyPath属性值,标志对象的该属性值唯一)
createIndex(indexName, objAttr, configObj)
创建一个索引indexName
// 索引名称[string]
objAttr
// 对象的属性名[string]
configObj
// 该索引的配置对象[object]
四、增加和删除数据
对数据库的操作(增删查改等)都需要通过事务来完成,事务具有三种模式:
readonly
只读(可以并发进行,优先使用)readwrite
读写versionchange
版本变更
向数据库中增加数据
前面提到,增加数据需要通过事务,事务的使用方式如下:
12345678910 | var transaction = db.transaction(['movies'], 'readwrite');transaction.oncomplete = function(event) { console.log('事务完成!');};transaction.onerror = function(event) { console.log('事务失败!', event.target.errorCode);};transaction.onabort = function(event) { console.log('事务回滚!');}; |
数据库对象的transaction()
方法接收两个参数:
storeNames
// 对象存储空间,可以是对象存储空间名称的数组,也可以是单个对象存储空间名称,必传[array|string]
mode
// 事务模式,上面提到的三种之一,可选,默认值是readonly
[string]
这样,我们得到一个事务对象transaction
, 有三种事件可能会被触发: complete
, error
, abort
. 现在,我们通过事务向数据库indexedDB-test
的 对象存储空间movies
中插入数据:
123456789101112131415161718192021222324 | var objectStore = transaction.objectStore('movies'); // 指定对象存储空间var data = [{ "title": "寻梦环游记", "year": "2017", "alt": "https://movie.douban.com/subject/20495023/", "id": "20495023"}, { "title": "你在哪", "year": "2016", "alt": "https://movie.douban.com/subject/26639033/", "id": "26639033"}, { "title": "笔仙咒怨", "year": "2017", "alt": "https://movie.douban.com/subject/27054612/", "id": "27054612"}];data.forEach(function(item, index){ var request = objectStore.add(item); request.onsuccess = function(event) { console.log('插入成功!', index); console.log(event.target.result, item.id); // add()方法调用成功后result是被添加的值的键(id) };}); |
通过事务对象transaction
,在objectStore()
方法中指定对象存储空间,就得到了可以对该对象存储空间进行操作的对象objectStore
.
向数据库中增加数据,add()
方法增加的对象,若是数据库中已存在相同的主键,或者唯一性索引的键值重复,则该条数据不会插入进去;
增加数据还有一个方法: put()
, 使用方法和add()
不同之处在于,数据库中若存在相同主键或者唯一性索引重复,则会更新该条数据,否则插入新数据。
从数据库中删除数据
删除数据使用delete
方法,同上类似:
12345678 | var request = db.transaction(['movies'], 'readwrite') .objectStore('movies') .delete('27054612'); // 通过键id来删除request.onsuccess = function(event) { console.log('删除成功!'); console.log(event.target.result);}; |
从数据中获取数据
获取数据使用get
方法,同上类似:
1234567 | var request = db.transaction('movies') .objectStore('movies') .get('9999682'); // 通过键alt来获取request.onsuccess = function(event) { console.log('获取成功!', event.target.result);}; |
五、使用索引
在前面,我们创建了两个索引alt
和title
, 配置对象里面的unique
属性标志该值是否唯一
现在我们想找到alt
属性值为https://movie.douban.com/subject/26639033/
的对象,就可以使用索引。
1234567891011121314 | var alt = 'https://movie.douban.com/subject/26639033/';var objectStore = db.transaction('movies').objectStore('movies'); // 打开对象存储空间var index = objectStore.index('alt'); // 使用索引'alt'var request = index.get(alt); // 创建一个查找数据的请求request.onsuccess = function(event) { console.log('The result is:', event.target.result);};var noDataTest = index.get('testalt'); // 没有该对象时的测试noDataTest.onsuccess = function(event) { console.log('success! result:', event.target.result);};noDataTest.onerror = function(event) { console.log('error! event:', event);}; |
使用唯一性索引,我们可以得到唯一的一条数据(或者undefined
),那么使用非唯一性索引呢?
我们向数据库中插入一条数据,使title
重复:
1234567 | db.transaction('movies', 'readwrite').objectStore('movies').add({ alt: 'https://movie.douban.com/subject/27054612121/', title: '寻梦环游记', year: '2017', id: '123456789'}).onsuccess = function(event) { console.log('插入成功!'); }; |
使用索引title
获取title
值为寻梦环游记的对象:
1234567 | var indexName = 'title', title = '寻梦环游记';var objectStore = db.transaction('movies').objectStore('movies');var index = objectStore.index(indexName); // 使用索引'alt'var request = index.get(title); // 创建一个查找数据的请求request.onsuccess = function(event) { console.log('The result is:', event.target.result);}; |
我们得到的是键值最小的那个对象.
使用一次索引,我们只能得到一条数据; 如果我们需要得到所有title
属性值为寻梦环游记
的对象,我们可以使用游标.
六、使用游标
得到一个可以操作游标的请求对象有两个方法:
openCursor(keyRange, direction)
openKeyCursor(keyRange, direction)
这两个方法接收的参数一样, 两个参数都是可选的: 第一个参数是限制值得范围,第二个参数是指定游标方向
游标的使用有以下几处:
- 在对象存储空间上使用:
var cursor = objectStore.openCursor()
- 在索引对象上使用:
var cursor = index.openCursor()
在对象存储空间上使用游标
使用游标常见的一种模式是获取对象存储空间上的所有数据.
123456789101112 | var list = [];var objectStore = db.transaction('movies').objectStore('movies');objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; if (cursor) { console.log('cursor:', cursor); list.push(cursor.value); cursor.continue(); } else { console.log('Get all data:', list); }}; |
使用游标时,需要在成功回调里拿到result
对象,判断是否取完了数据:若数据已取完,result
是undefined
; 若未取完,则result
是个IDBCursorWithValue
对象,需调用continue()
方法继续取数据。 也可以根据自己需求, 对数据进行过滤。
在indexedDB2
规范中,在对象存储空间对象上纳入了一个getAll()
方法,可以获取所有对象:
123 | objectStore.getAll().onsuccess = function(event) { console.log('result:', event.target.result);}; |
在索引上使用游标
接着本文上述使用索引的例子,在索引title
上使用openCursor()
方法时,若不传参数,则会遍历所有数据,在成功回调中的到的result
对象有以下属性:
key
数据库中这条对象的title
属性值primaryKey
数据库中这条对象的alt
值value
数据库中这条对象direction
openCursor()方法传入的第二个对象,默认值为next
source
IDBIndex对象 举例如下:
12345678910 var index = db.transaction('movies').objectStore('movies').index('title');index.openCursor().onsuccess = function(event) { var cursor = event.target.result; if (cursor) { console.log('cursor:', cursor); cursor.continue(); }};
在索引title
上使用openKeyCursor()
方法,若不传参数,同样也会遍历所有数据,result
对象属性如下:
key
数据库中这条对象的title
属性值primaryKey
数据库中这条对象的alt
值direction
openCursor()方法传入的第二个对象,默认值为next
source
altBIndex对象
和openCursor()
方法相比,得到的数据少一个value
属性,是没有办法得到存储对象的其余部分
前面说到,我们要根据索引title
获取所有title
属性值为寻梦环游记
的对象,要使用游标,而又不想遍历所有数据,这时就要用到openCursor()
的第一个参数: keyRange
keyRange
是限定游标遍历的数据范围,通过IDBKeyRange
的一些方法设置该值:
1234567891011121314 | var singleKeyRange = IDBKeyRange.only("寻梦环游记"), list = [];var index = db.transaction('movies').objectStore('movies').index('title');index.openCursor(singleKeyRange).onsuccess = function(event) { var cursor = event.target.result; if (cursor) { console.log('cursor.value:', cursor.value); list.push(cursor.value); cursor.continue(); } else { console.log('list:', list); }}; |
IDBKeyRange
其他一些方法:
1234567891011 | // 匹配所有在 "Bill" 前面的, 包括 "Bill"var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill"); // 匹配所有在 “Bill” 前面的, 但是不需要包括 "Bill"var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true); // 匹配所有在'Donna'后面的, 但是不包括"Donna"var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true); // 匹配所有在"Bill" 和 "Donna" 之间的, 但是不包括 "Donna"var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true); |
更多请参考 MDN|IDBKeyRange
游标默认遍历方向是按主键从小到大,有时候我们倒序遍历,此时可以给openCursor()
方法传递第二个参数: direction
: next
|nextunique
|prev
|prevunique
1234567891011121314 | var singleKeyRange = IDBKeyRange.only("寻梦环游记"), list = [];var index = db.transaction('movies').objectStore('movies').index('title');index.openCursor(singleKeyRange, 'prev').onsuccess = function(event) { var cursor = event.target.result; if (cursor) { console.log('cursor.value:', cursor.value); list.push(cursor.value); cursor.continue(); } else { console.log('list:', list); }}; |
传了prev
的结果是按倒序遍历的.
因为 “name” 索引不是唯一的,那就有可能存在具有相同 name 的多条记录。 要注意的是这种情况不可能发生在对象存储空间上,因为键必须永远是唯一的。 如果你想要在游标在索引迭代过程中过滤出重复的,你可以传递
nextunique
(或prevunique
, 如果你正在向后寻找)作为方向参数。 当nextunique
或是prevunique
被使用时,被返回的那个总是键最小的记录。
1234567891011121314 | var singleKeyRange = IDBKeyRange.only("寻梦环游记"), list = [];var index = db.transaction('movies').objectStore('movies').index('title');index.openCursor(singleKeyRange, 'prevunique').onsuccess = function(event) { var cursor = event.target.result; if (cursor) { console.log('cursor.value:', cursor.value); list.push(cursor.value); cursor.continue(); } else { console.log('list:', list); }}; |
七、关闭和删除数据库
- 关闭数据库只需要在数据库对象
db
上调用close()
方法即可
1 db.close();
关闭数据库后,db
对象仍然保存着该数据库的相关信息,只是无法再开启事务(调用开启事务方法会报错,提示数据库连接已断开):
- 删除数据库则需要使用
indexedDB.deleteDatabase(dbName)
方法
JavaScript1 window.indexedDB.deleteDatabase(dbName);
八、indexedDB的局限性
以下情况不适合使用IndexedDB
- 全球多种语言混合存储。国际化支持不好。需要自己处理。
- 和服务器端数据库同步。你得自己写同步代码。
- 全文搜索。
注意,在以下情况下,数据库可能被清除:
- 用户请求清除数据。
- 浏览器处于隐私模式。最后退出浏览器的时候,数据会被清除。
- 硬盘等存储设备的容量到限。
- 不正确的
- 不完整的改变.
总结
- 使用
indexedDB.open(dbName, version)
打开一个数据库连接 - 使用
indexedDB.deleteDatabase(dbName)
删除一个数据库 - 在数据库对象
db
上使用createObjectStore(storeName, config)
创建对象存储空间 - 在对象存储空间
objectStore
上使用createIndex(indexName, keyName, config)
创建索引 - 对数据库的操作都需要通过事务完成:
var transction = db.transaction([storeName], mode)
- 数据库的增删改查均通过
objectStore
对象完成,var objectStore = transaction.objectStore(storeName)
- 对数据库数据操作有:
add()
、get()
、delete()
、put
等方法 - 查找数据可以使用索引:
objectStore.index(indexName)
- 遍历和过滤数据可以使用游标:
openCursor(keyRange, direction)
参考链接
- IndexedDB的基本概念-MDN
- 使用 IndexedDB-MDN
- IndexedDB API接口-MDN
- Indexed Database API 2.0 – w3c
- 01-11全球最受赞誉公司揭晓:苹果连续九年第一
- 12-09罗伯特·莫里斯:让黑客真正变黑
- 12-09谁闯入了中国网络?揭秘美国绝密黑客小组TA
- 12-09警示:iOS6 惊现“闪退”BUG
- 12-25优酷推出U镜到底等直播功能 已应用在羽毛球
- 12-25百川智能正式发布全链路领域增强大模型
- 12-25SHEIN4家仓储物流园获“零废工厂”认证
- 12-25西方博主在TikTok上展现中国风貌,“China
- 12-05亚马逊推出新一代基础模型 任意模态生成大模