摘要:设置为参数设置指定索引,并确保唯一性上面主要做了件事打开数据库表新建,并设置设置打开数据库表主要就是版本号和名字,没有太多讲的,我们直接从创建开始吧。打开注意事项检查是否支持版本更新在生成一个实例时,需要手动指定一个版本号。
在知乎和我在平常工作中,常常会看到一个问题:
前端现在还火吗?
这个我只想说:
隔岸观火的人永远无法明白起火的原因,只有置身风暴,才能找到风眼之所在 ——『秦时明月』
你 TM 看都不看前端现在的发展,怎么去评判前端火不火,我该不该尝试一下其他方面的内容呢?本人为啥为这么热衷于新的技术呢?主要原因在于,生怕会被某一项颠覆性的内容淘汰掉,从前沿领域掉队下来。说句人话就是:穷,所以只能学了...。所以本文会从头剖析一下 IndexedDB 在前端里面的应用的发展。
indexedDB 目前在前端慢慢得到普及和应用。它正朝着前端离线数据库技术的步伐前进。以前一开始是 manifest、localStorage、cookie 再到 webSQL,现在 indexedDB 逐渐被各大浏览器认可。我们也可以针对它来进行技术上创新的开发。比如,现在小视频非常流行,那么我们可以在用户观看时,通过 cacheStorage 缓存,然后利用 WebRTC 技术实现 P2P 分发的控制,不过需要注意,一定要合理利用大小,不然后果真的很严重。
indexedDB 的整体架构,是由一系列多带带的概念串联而成,全部概念如下列表。一眼看去会发现没有任何逻辑,不过,这里我顺手画了一幅逻辑图,中间会根据 函数 的调用而相互串联起来。
IDBRequest
IDBFactory
IDBDatabase
IDBObjectStore
IDBIndex
IDBKeyRange
IDBCursor
IDBTransaction
整体逻辑图如下:
TL;DR下文主要介绍了 indexedDB 的基本概念,以及在实际应用中的实操代码。
indexedDB 基础概念。在 indexedDB 里面会根据索引 index 来进行整体数据结构的划分。
indexedDB 数据库的更新是一个非常蛋疼的事情,因为,Web 的灵活性,你既需要做好向上版本的更新,也需要完善向下版本的容错性。
indexedDB 高效索引机制,在内部,indexedDB 已经提供了 index、cursor等高效的索引机制,推荐不要直接将所有数据都取回来,再进行筛选,而是直接利用 cursor 进行。
最后推荐几个常用库
离线存储IndexedDB 可以存储非常多的数据,比如 Object,files,blobs 等,里面的存储结构是根据 Database 来进行存储的。每个 DB 里面可以有不同的 object stores。具体结构如下图:
并且,我们可以给 key 设定相关特定的值,然后在索引的时候,可以直接通过 key 得到具体的内容。使用 IndexDB 需要注意,其遵循的是同域原则。
indexDB 基本概念在 indexDB 中,有几个基本的操作对象:
Database: 通过 open 方法直接打开,可以得到一个实例的 DB。每个页面可以创建多个 DB,不过一般都是一个。
idb.open(name, version, upgradeCallback)
Object store: 这个就是 DB 里面具体存储的对象。这个可以对应于 SQL 里面的 table 内容。其存储的结构为:
index: 有点类似于外链,它本身是一种 Object store,主要是用来在本体的 store 中,索引另外 object store 里面的数据。需要区别的是,key 和 index 是不一样的。可以参考: index DEMO,mdn index。如下图表示:
如下 code 为:
// 创建 index var myIndex = objectStore.index("lName");
transaction: 事务其实就是一系列 CRUD 的集合内容。如果其中一个环节失败了,那么整个事务的处理都会被取消。例如:
var trans1 = db.transaction("foo", "readwrite"); var trans2 = db.transaction("foo", "readwrite"); var objectStore2 = trans2.objectStore("foo") var objectStore1 = trans1.objectStore("foo") objectStore2.put("2", "key"); objectStore1.put("1", "key");
cursor: 主要是用来遍历 DB 里面的数据内容。主要是通过 openCursor 来进行控制。
function displayData() { var transaction = db.transaction(["rushAlbumList"], "readonly"); var objectStore = transaction.objectStore("rushAlbumList"); objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; if(cursor) { var listItem = document.createElement("li"); listItem.innerHTML = cursor.value.albumTitle + ", " + cursor.value.year; list.appendChild(listItem); cursor.continue(); } else { console.log("Entries all displayed."); } }; }如何使用 IndexDB
上面说了几个基本的概念。那接下来我们实践一下 IndexDB。实际上入门 IndexDB 就是做几个基本的内容
打开数据库表
设置指定的 primary Key
定义好索引的 index
前期搭建一个 IndexedDB 很简单的代码如下:
var request = indexedDB.open(dbName, 2); request.onerror = function(event) { // 错误处理程序在这里。 }; request.onupgradeneeded = function(event) { var db = event.target.result; // 设置 id 为 primaryKey 参数 var objectStore = db.createObjectStore("customers", { keyPath: "id",{autoIncrement:true} }); // 设置指定索引,并确保唯一性 objectStore.createIndex("name", "name", { unique: false }); objectStore.createIndex("email", "email", { unique: true }); };
上面主要做了 3 件事:
打开数据库表
新建 Store,并设置 primary Key
设置 index
打开数据库表主要就是版本号和名字,没有太多讲的,我们直接从创建 store 开始吧。
创建 Object Store使用的方法就是 IDBDatabase 上的 createObjectStore 方法。
var objectStore = db.createObjectStore("customers", { keyPath: "id",{autoIncrement:true} });
基本函数构造为:
IDBObjectStore createObjectStore(DOMString name, optional IDBObjectStoreParameters options) dictionary IDBObjectStoreParameters { (DOMString or sequence)? keyPath = null; boolean autoIncrement = false; };
keyPath: 用来设置主键的 key,具体区别可以参考下面的 keyPath 和 generator 的区别。
autoIncrement: 是否使用自增 key 的特性。
创建的 key 主要是为了保证,在数据插入时唯一性的标识。
不过,往往一个主键(key),是没办法很好的完成索引,在具体实践时,就还需要辅键 (aid-key) 来完成辅助索引工作,这个在 IndexDB 就映射为 index。
设置索引 index在完成 PK(Primary key) 创建完毕后,为了更好的搜索性能我们还需要额外创建 index。这里可以直接使用:
objectStore.createIndex("indexName", "property", options);
indexName: 设置当前 index 的名字
property: 从存储数据中,指明 index 所指的属性。
其中,options 有三个选项:
unique: 当前 key 是否能重复 (最常用)
multiEntry: 设置当前的 property 为数组时,会给数组里面每个元素都设置一个 index 值。
# 创建一个名字叫 titleIndex 的 index,并且存储的 index 不能重复 DB.createIndex("titleIndex", "title", {unique: false});
具体可以参考:MDN createIndex Prop 和 googleDeveloper Index。
增删数据在 IndexedDB 里面进行数据的增删,都需要在 transaction 中完成。而这个增删数据,大家可以理解为一次 request,相当于在一个 transaction 里面管理所有当前逻辑操作的 request。所以,在正式开始进行数据操作之前,还需要给大家简单介绍一些如果创建一个事务。
事务的创建transaction API,如下 [代码1]。在创建时,你需要手动指定当前 transaction 是那种类型的操作,基本的内容有:
"readonly":只读
"readwrite":读写
"versionchange":这个不能手动指定,会在 upgradeneeded 回调事件里面自动创建。它可以用来修改现有 object store 的结构数据,比如 index 等。
你可以通过在数据库打开之后,通过 IDBDataBase 上的 transaction 方法创建,如 [代码2]。
[代码1] [NewObject] IDBTransaction transaction((DOMString or sequence) storeNames, optional IDBTransactionMode mode = "readonly"); [代码2] var transaction = db.transaction(["customers"], "readwrite"); var objectStore = transaction.objectStore("customers"); # 遍历存储数据 for (var i in customerData) { var request = objectStore.add(customerData[i]); request.onsuccess = function(event) { // success, done? }; }
事务在创建的时候不仅仅可以制定执行的模式,还可以指定本次事务能够影响的 ObjectStore 范围,具体细节就是在第一个 transaction 参数里面传入的是一个数据,然后通过 objectStore() 方法打开多个 OS 进行操作,如下 [代码3]。
[代码3] var tx = db.transaction(["books","person"], "readonly"); var books = tx.objectStore("books"); var person = tx.objectStore("person");操作数据
完成了事务的创建之后,我们就可以正式的开始进行数据的交互操作了,也就是写我们具体的业务逻辑。如下 [代码1],一个完整数据事务的操作。
[代码1] var tx = db.transaction("books", "readwrite"); var store = tx.objectStore("books"); store.put({title: "Quarry Memories", author: "Fred", isbn: 123456}); store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567}); store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678}); tx.oncomplete = function() { // All requests have succeeded and the transaction has committed. };
通过 objectStore 回调得到的 IDBObjectStore 对象,我们就可以进行一些列的增删查改操作了。可以参考 [代码2]。详细的可以参考文末的 appendix。
[代码2] [NewObject] IDBRequest put(any value, optional any key); [NewObject] IDBRequest add(any value, optional any key); [NewObject] IDBRequest delete(any query);索引数据
索引数据是所有数据库里面最重要的一个。这里,我们可以使用游标,index 来做。例如,通过 index 来快速索引 key 值,参考 [代码1]。
[代码1] var index = objectStore.index("name"); index.get("Donna").onsuccess = function(event) { alert("Donna"s SSN is " + event.target.result.ssn); };
更详细的内容,可以参考下文 数据索引方式。
keyPath 和 key Generator何谓 keyPath 和 keyGenerator 应该算是 IndexedDB 里面比较难以理解的概念。简单来说,IndexedDB 在创建 Store 的时候,必须保证里面的数据是唯一的,那么得需要像其它数据库一样设置一个 primary Key 来区分不同数据。而 keyPath 和 Generator 就是两种不同的设置 key 的方式。
设置 keyPath
# 设置预先需要存放的数据 const customerData = [ { ssn: "444-44-4444", name: "Bill", age: 35, email: "bill@company.com" }, { ssn: "555-55-5555", name: "Donna", age: 32, email: "donna@home.org" } ]; # 通过 keyPath 设置 Primary Key var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });
因为 ssn 在该数据集是唯一的,所以,我们可以利用它来作为 keyPath 保证 unique 的特性。或者,可以设置为自增的键值,比如 id++ 类似的。
upgradeDb.createObjectStore("logs", {keyPath: "id", autoIncrement:true});
使用 generator
generator 会每次在添加数据时,自动创建一个 unique value。这个 unique value 是和你的实际数据是分开的。里面直接通过 autoIncrement:true 来设置即可。
upgradeDb.createObjectStore("notes", {autoIncrement:true});indexDB 打开注意事项
检查是否支持 indexDB
if (!("indexedDB" in window)) { console.log("This browser doesn"t support IndexedDB"); return; }
版本更新: indexDB
在生成一个 indexDB 实例时,需要手动指定一个版本号。而最常用的
idb.open("test-db7", 2, function(upgradeDb) {})
这样会造成一个问题,比如上线过程中,用户A第一次请求返回了新版本的网页,连接了版本2。之后又刷新网页命中了另一台未上线的机器,连接了旧版本1 出错。主要原因是:
indexedDB API 中不允许数据库中的数据仓库在同一版本中发生变化. 并且当前 DB 版本不能和低版本的 version 连接。
比如,你一开始定义的 DB 版本内容为:
# 版本一定义的内容 db.version(1).stores({friends: "++id,name"}); # 版本二修改结构为: db.version(2).stores({friends: "++id,name,shoeSize"});
如果此时,用户先打开了 version(1),但是后面,又得到的是 version(2) 版本的 HTML,这时就会出现 error 的错误。
参考:
版本更替
版本更新这个在 IndexDB 是一个很重要的问题。主要原因在于
indexedDB API 中不允许数据库中的数据仓库在同一版本中发生变化. 并且当前 DB 版本不能和低版本的 version 连接。
上面就可以抽象为一个问题:
你什么情况下需要更新 IndexDB 的版本呢?
该表数据库里面的 keyPath 时。
你需要重新设计数据库表结构时,比如新增 index
# 版本 1 的 DB 设计,有一个主键 id 和 index-name db .version(1) .stores({friends: "++id,name"}) # 如果直接想新增一个 key,例如 male,是无法成功的 db .version(1) .stores({friends: "++id,name,male"}) # 正确办法是直接修改版本号更新 db .version(2) .stores({friends: "++id,name,male"})
不过,如果直接修改版本号,会出现这样一个 case:
由于原始 HTML 更新问题,用户首先访问的是版本 1 的 A 页面,然后,访问更新过后的 B 页面。这时,IndexDB 成功更新为高版本。但是,用户下次又命中了老版本的 A 页面,此时 A 中还是连接低版本的 IndexDB ,就会报错,导致你访问失败。
解决办法就是,设置过滤,在 open 的时候,手动传入版本号:
# 打开版本 1 的数据库 var dbPromise = idb.open("db1", 1, function(upgradeDb){...}) # 打开版本 2 的数据库 var dbPromise = idb.open("db2", 2, function(upgradeDb){...})
不过,这样又会造成另外一个问题,即,数据迁移(老版本数据,不可能不要吧)。这里,IndexDB 会有一个 updateCallback 给你触发,你可以直接在里面做相关的数据迁移处理。
var dbPromise = idb.open("test-db7", 2, function(upgradeDb) { switch (upgradeDb.oldVersion) { case 0: upgradeDb.createObjectStore("store", {keyPath: "name"}); case 1: var peopleStore = upgradeDb.transaction.objectStore("store"); peopleStore.createIndex("price", "price"); } });
在使用的时候,一定要注意 DB 版本的升级处理,比如有这样一个 case,你的版本已经是 3,不过,你需要处理版本二的数据:
# 将版本二 中的 name 拆分为 firstName 和 lastName db.version(3).stores({friends: "++id,shoeSize,firstName,lastName"}).upgrade(function(t) { return t.friends.toCollection().modify(function(friend) { // Modify each friend: friend.firstName = friend.name.split(" ")[0]; friend.lastName = friend.name.split(" ")[1]; delete friend.name; }); });
对于存在版本 2 数据库的用户来说是 OK 的,但是对于某些还没有访问过你数据库的用户来说,这无疑就报错了。解决办法有:
保留每个版本时,创建的字段和 stores
在更新 callback 里面,对处理的数据判断是否存在即可。
在 Dexie.js DB 数据库中,需要你保留每次 DB 创建的方法,实际上是通过 添加 swtich case ,来完成每个版本的更新:
# Dexie.js 保留 DB 数据库 db.version(1).stores({friends: "++id,name"}); db.version(2).stores({friends: "++id,name,shoeSize"}); db.version(3).stores({friends: "++id,shoeSize,firstName,lastName"}).upgrade(...) # 内部原理,直接添加 switch case 完成版本更新 var dbPromise = idb.open("test-db7", 2, function(upgradeDb) { switch (upgradeDb.oldVersion) { case 0: upgradeDb.createObjectStore("store", {keyPath: "name"}); case 1: var peopleStore = upgradeDb.transaction.objectStore("store"); peopleStore.createIndex("price", "price"); } });
如果遇到一个页面打开,但是另外一个页面拉取到新的代码进行更新时,这个时候还需要将低版本 indexedDB 进行显式的关闭。具体操作办法就是监听 onversionchange 事件,当版本升级时,通知当前 DB 进行关闭,然后在新的页面进行更新操作。
openReq.onupgradeneeded = function(event) { // 所有其它数据库都已经被关掉了,直接更新代码 db.createObjectStore(/* ... */); db.onversionchange = function(event) { db.close(); }; }
最后,更新是还有几个注意事项:
版本更新不能改变 primary key
回退代码时,千万注意版本是否已经更新。否则,只能增量更新,重新修改版本号来修复。
存储加密特性有时候,我们存储时,想得到一个由一串 String 生成的 hash key,那在 Web 上应该如何实现呢?
这里可以直接利用 Web 上已经实现的 WebCrypto,为了实现上述需求,我们可以直接利用里面的 digest 方法即可。这里 MDN 上,已经有现成的办法,我们直接使用即可。
参考:
WebCrypto 加密手段
存储上限值基本限制为:
浏览器 | 限制 |
---|---|
Chrome | 可用空间 < 6% |
Firebox | 可用空间 < 10% |
Safari | < 50MB |
IE10 | < 250MB |
逐出策略为:
浏览器 | 逐出政策 |
---|---|
Chrome | 在 Chrome 耗尽空间后采用 LRU 策略 |
Firebox | 在整个磁盘已装满时采用 LRU 策略 |
Safari | 无逐出 |
Edge | 无逐出 |
参考:
存储上限值
浏览器内核存储上限值处理
在数据库中除了基本的 CRUD 外,一个高效的索引架构,则是里面的重中之重。在 indexedDB 中,我们一共可以通过三种方式来索引数据:
固定的 key 值
索引外键(index)
游标(cursor)
固定 key 索引IDBObjectStore 提供给了我们直接通过 primaryKey 来索引数据,参考 [代码1],这种方式需要我们一开始就知道目标的 key 内容。当然,也可以通过 getAll 全部索引数据。
[代码1] [NewObject] IDBRequest get(any query); [NewObject] IDBRequest getKey(any query); [NewObject] IDBRequest getAll(optional any query, optional [EnforceRange] unsigned long count); [NewObject] IDBRequest getAllKeys(optional any query, optional [EnforceRange] unsigned long count);
比如,我们通过 primaryKey 得到一条具体的数据:
db.transaction("customers").objectStore("customers").get("id_card_1118899").onsuccess = function(event) { // data is event.target.result.name };
也可以 fetch 整个 Object Store 的数据。这些场景用处比较少,这里就不过多讲解。我们主要来了解一下 index 的索引方式。
index 索引如果想要查询某个数据,直接通过整个对象来进行遍历的话,这样做性能耗时是非常大的。如果我们结合 index 来将 key 加以分类,就可以很快速的实现指定数据的索引。这里,我们可以直接利用 IDBObjectStore 上面的 index() 方法来获取指定 index 的值,具体方法可以参考 [代码1]。
[代码1] IDBIndex index(DOMString name);
该方法会直接返回一个 IDBIndex 对象。这你也可以理解为一个类似 ObjectStore 的微型 index 数据内容。接着,我们可以使用 get() 方法来获得指定 index 的数据,参考[代码2]。
[代码2] var index = objectStore.index("name"); index.get("Donna").onsuccess = function(event) { alert("Donna"s SSN is " + event.target.result.ssn); };
使用 get 方法不管你的 index 是否是 unique 的都会只会返回第一个数据。如果想得到多个数据的话,可以使用 getAll(key) 来做。通过 getAll() 得到的回调函数,直接通过 event.target.result 可以得到对应的 value 内容。
objectStore.getAll().onsuccess = function(event) { printf(event.target.result); // Array };
除了通过 getAll() 得到所有数据外,还可以采用更高效的 cursor 方法遍历得到的数据。
参考:
getAll() 和 openCursor 实例
游标索引所谓的游标,大家心里应该可以有一个初步的印象,就像我们物理尺子上的那个东西,可以自由的移动,来标识指向的对象内容。cursor 里面有两个核心的方法:
advance(count): 将当前游标位置向前移动 count 位置
continue(key): 将当前游标位置移动到指定 key 的位置,如果没提供 key 则代表的移动下一个位置。
比如,我们使用 cursor 来遍历 Object Store 的具体数据。
objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; if(cursor) { // cursor.key // cursor.value cursor.continue(); } else { console.log("Entries all displayed."); } };
通常,游标可以用来遍历两个类型的数据,一个是 ObjectStore、一个是 Index。
Object.store: 如果在该对象上使用游标,那么会根据 primaryKey 遍历整个数据,注意,这里不会存在重复的情况,因为 primaryKey 是唯一的。
index: 在 index 上使用游标的话,会以当前的 index 来进行遍历,其中可能会存在重复的现象。
在 IDBObjectStore 对象上有两种方法来打开游标:
openCursor: 遍历的对象是 具体的数据值,最常用的方法
openKeyCursor: 遍历的对象是 数据 key 值
这里,我们通过 openCursor 来直接打开一个 index 数据集,然后进行遍历。
PersonIndex.openCursor().onsuccess = function(event) { var cursor = event.target.result; if (cursor) { customers.push(cursor.value); cursor.continue(); } else { alert("Got all customers: " + customers); } };
在游标中,还提供给了一个 update 和 delete 方法,我们可以用它来进行数据的更新操作,否则的话就直接使用 ObjectStore 提供的 put 方法。
游标里面我们还可以限定其遍历的范围和方向。这个设置是我们直接在 openCursor() 方法里面传参完成的,该方法的构造函数参考 [代码1]。他里面可以传入两个参数,第一个用来指定范围,第二个用来指定 cursor 移动的方向。
[代码1] IDBRequest openCursor(optional any query, optional IDBCursorDirection direction = "next");
如果需要对 cursor 设置范围的话,就需要使用到 IDBKeyRange 这个对象,使用样板可以参考 [代码2]。IDBKeyRange 里面 key 参考的对象 因使用者的不同而不同。如果是针对 ObjectStore 的话,则是针对 primaryKey,如果是针对 Index 的话,则是针对当前的 indexKey
/ 匹配所有在 “Bill” 前面的, 但是不需要包括 "Bill" var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true);
比如,我们这里对 PersonIndex 设置一个 index 范围,即,索引 在 villainhr 和 jimmyVV 之间的数据集合。
# 都包括 villainhr 和 jimmyVV 的数据 var boundKeyRange = IDBKeyRange.bound("villainhr", "jimmyVV", true, true); PersonIndex.openCursor(boundKeyRange).onsuccess = function(event) { var cursor = event.target.result; if (cursor) { // Do something with the matches. cursor.continue(); } };
如果你还想设置遍历的方向和是否排除重复数据,还可以根据 [代码2] 的枚举类型来设置。比如,在 [代码3] 中,我们改变默认的 cursor 遍历数据的方向为 prev,从末尾开始。
[代码2] enum IDBCursorDirection { "next", "nextunique", "prev", "prevunique" }; [代码3] objectStore.openCursor(null, IDBCursor.prev).onsuccess = function(event) { var cursor = event.target.result; if (cursor) { // cursor.value cursor.continue(); } };事务读取性能
在 indexDB 里面的读写全部是基于 transaction 模式来的。也就是 IDBDataBase 里面的 transaction 方法,如下 [代码1]。所有的读写都可以比作在 transaction 作用域下的请求,只有当所有请求完成之后,该次 transaction 才会生效,否则就会抛出异常或者错误。transaction 会根据监听 error,abort,以及 complete 三个事件来完成整个事务的流程管理,参考[代码2]。
[代码1] [NewObject] IDBTransaction transaction((DOMString or sequence) storeNames, optional IDBTransactionMode mode = "readonly"); [代码2] attribute EventHandler onabort; attribute EventHandler oncomplete; attribute EventHandler onerror;
例如:
var request = db.transaction(["customers"], "readwrite") .objectStore("customers") .delete("gg"); request.onsuccess = function(event) { // delete, done };
你可以在 transaction 方法里面手动传入 readwrite 或者其他表示事务的 readonly 参数,来表示本次事务你会进行如何的操作。IndexedDB 在初始设计时,就已经决定了它的性能问题。
只含有 readonly 模式的 transaction 可以并发进行执行
含有 write 模式的 transaction 必须按照队列 来 执行
这就意味着,如果你使用了 readwrite 模式的话,那么后续不管是不是 readonly 都必须等待该次 transaction 完成才行。
常用技巧 生成 id++ 的主键指定 primaryKey 生成时,是通过 createObjectStore 方法来操作的。有时候,我们会遇到想直接得到一个 key,并且存在于当前数据集中,可以在 options 中同时加上 keyPath 和 autoIncrement 属性。该 key 的范围是 [1- $ 2^{53} $],参考 keygenerator key 的大小
db.createObjectStore("table1", {keyPath: "id", autoIncrement: true});推荐
阅读推荐
indexedDB W3C 文档
indexedDB 入门
MDN indexedDB 入门
好用库推荐
idb: 一个 promise 的 DB 库
Indexed AppendixIndexedDB 数据库使用key-value键值对储存数据.你可以对对象的某个属性创建索引(index)以实现快速查询和列举排序。.key可以使二进制对象
IndexedDB 是事务模式的数据库. IndexedDB API提供了索引(indexes), 表(tables), 指针(cursors)等等, 但是所有这些必须是依赖于某种事务的。
The IndexedDB API 基本上是异步的.
IndexedDB 数据库的请求都会包含 onsuccess和onerror事件属性。
IndexedDB 在结果准备好之后通过DOM事件通知用户
IndexedDB是面向对象的。indexedDB不是用二维表来表示集合的关系型数据库。这一点非常重要,将影响你设计和建立你的应用程序。
indexedDB不使用结构化查询语言(SQL)。它通过索引(index)所产生的指针(cursor)来完成查询操作,从而使你可以迭代遍历到结果集合。
IndexedDB遵循同源(same-origin)策略
局限和移除 case全球多种语言混合存储。国际化支持不好。需要自己处理。
和服务器端数据库同步。你得自己写同步代码。
全文搜索。
在以下情况下,数据库可能被清除:
用户请求清除数据。
浏览器处于隐私模式。最后退出浏览器的时候,数据会被清除。
硬盘等存储设备的容量到限。
不正确的
不完整的改变.
常规概念数据库
数据库: 通常包含一个或多个 object stores. 每个数据库必须包含以下内容:
名字(Name): 它标识了一个特定源中的数据库,并且在数据库的整个生命周期内保持不变. 此名字可以为任意字符串值(包括空字符串).
当前版本(version). 当一个数据库首次创建时,它的 version 为1,除非另外指定. 每个数据库在任意时刻只能有一个 version
对象存储(object store): 用来承载数据的一个分区.数据以键值对形式被对象存储永久持有。在 OS 中,创建一个 key 可以使用 key generator 和 key path。
key generator: 简单来说就是在存储数据时,主动生成一个 id++ 来区分每条记录。这种情况下 存储数据的 key 是和 value 分开进行存储的,也就是 (out of line)。
key path: 需要用户主动来设置储存数据的 key 内容,
request: 每次读写操作,可以当做一次 request.
transaction: 一系列读写请求的集合。
index: 一个特殊的 Object Store,用来索引另外一个 Store 的数据。
具体数据 key/value
key: 这个 key 的值,可以通过三种方式生成。 a key generator, a key path, 用户指定的值。并且,这个 key 在当前的 Object Store 是唯一的。一个 key 类型可以是 string, date, float, and array 类型。不过,在老版本的时候,一般只支持 string or integer。(现在,版本应该都 OK 了)
- key generator: 相当于以一种 `id++` 的形式来生成一个 key 值。 - key path: 当前指定的 key 可以根据 value 里面的内容来指定。里面可以为一些分隔符。 - 指定的 key:这个就是需要用户手动来指定生成。
value: 可以存储 boolean, number, string, date, object, array, regexp, undefined, and null。现在还可以存储 files and blob 对象。
操作作用域
scope:这可以比作 transaction 的作用域,即,一系列 transaction 执行的顺序。该规定,多个 reading transaction 能够同时执行。但是 writing 则只能排队进行。
key range: 用来设置取出数据的 key 的范围内容。
参考:
原生概念 IndexedDB
IDBFactory这其实就是 indexDB 上面挂载的对象。主要 API 如下:
[Exposed=(Window,Worker)] interface IDBFactory { [NewObject] IDBOpenDBRequest open(DOMString name, optional [EnforceRange] unsigned long long version); [NewObject] IDBOpenDBRequest deleteDatabase(DOMString name); short cmp(any first, any second); };
你可以直接通过 open 来打开一个数据库。通过 返回一个 Request 对象,来进行结果监听的回调:
var request = indexedDB.open("AddressBook", 15); request.onsuccess = function(evt) {...}; request.onerror = function(evt) {...};
参考:
IndexDB Factory API
IDBRequest当你通过 open 方法处理过后,就会得到一个 Request 回调对象。这个就是 IDBRequest 的实例。
[Exposed=(Window,Worker)] interface IDBRequest : EventTarget { readonly attribute any result; // 通过 open 打开过后的 IDBObjectStore 实例 readonly attribute DOMException? error; readonly attribute (IDBObjectStore or IDBIndex or IDBCursor)? source; readonly attribute IDBTransaction? transaction; readonly attribute IDBRequestReadyState readyState; // Event handlers: attribute EventHandler onsuccess; attribute EventHandler onerror; }; enum IDBRequestReadyState { "pending", "done" }; [Exposed=(Window,Worker)] interface IDBOpenDBRequest : IDBRequest { // Event handlers: attribute EventHandler onblocked; attribute EventHandler onupgradeneeded; };
你可以通过 result 得到当前数据库操作的结果。如果你打开更新后的版本号的话,还需要监听 onupgradeneeded 事件来实现。最常通过 indexedDB.open 遇见的错误就是 VER_ERR 版本错误。这表明存储在磁盘上的数据库的版本高于你试图打开的版本。
db.onerror = function(event) { // Generic error handler for all errors targeted at this database"s // requests! alert("Database error: " + event.target.errorCode); };
所以,一般在创建 IndexDB 时,还需要管理它版本的更新操作,这里就需要监听 onupgradeneeded 来是实现。
request.onupgradeneeded = function(event) { // 更新对象存储空间和索引 .... };
或者我们可以直接使用 idb 微型库来实现读取操作。
var dbPromise = idb.open("test-db3", 1, function(upgradeDb) { if (!upgradeDb.objectStoreNames.contains("people")) { upgradeDb.createObjectStore("people", {keyPath: "email"}); } if (!upgradeDb.objectStoreNames.contains("notes")) { upgradeDb.createObjectStore("notes", {autoIncrement: true}); } if (!upgradeDb.objectStoreNames.contains("logs")) { upgradeDb.createObjectStore("logs", {keyPath: "id", autoIncrement: true}); } });
其中通过 onupgradeneeded 回调得到的 event.result 就是 IDBDatabase 的实例,常常用来设置 index 和插入数据。参考下面内容。
参考:
IDBRequest API
IDBDatabase该对象常常用来做 Object Store 和 transaction 的创建和删除。该部分是 onupgradeneeded 事件获得的 event.target.result 对象:
request.onupgradeneeded = function(event) { // 更新对象存储空间和索引 .... // event.target.result 对象 };
具体 API 内容如下:
[Exposed=(Window,Worker)] interface IDBDatabase : EventTarget { readonly attribute DOMString name; readonly attribute unsigned long long version; readonly attribute DOMStringList objectStoreNames; [NewObject] IDBTransaction transaction((DOMString or sequence) storeNames, optional IDBTransactionMode mode = "readonly"); void close(); [NewObject] IDBObjectStore createObjectStore(DOMString name, optional IDBObjectStoreParameters options); void deleteObjectStore(DOMString name); // Event handlers: attribute EventHandler onabort; attribute EventHandler onclose; attribute EventHandler onerror; attribute EventHandler onversionchange; }; dictionary IDBObjectStoreParameters { (DOMString or sequence )? keyPath = null; boolean autoIncrement = false; };
如果它通过 createObjectStore 方法,那么得到的就是一个 IDBObjectStore 实例对象。如果是 transaction 方法,那么就是 IDBTransaction 对象。
IDBObjectStore该对象一般是用来创建 index 和插入数据使用。
可以参考:
[Exposed=(Window,Worker)] interface IDBObjectStore { attribute DOMString name; readonly attribute any keyPath; readonly attribute DOMStringList indexNames; [SameObject] readonly attribute IDBTransaction transaction; readonly attribute boolean autoIncrement; [NewObject] IDBRequest put(any value, optional any key); [NewObject] IDBRequest add(any value, optional any key); [NewObject] IDBRequest delete(any query); [NewObject] IDBRequest clear(); [NewObject] IDBRequest get(any query); [NewObject] IDBRequest getKey(any query); [NewObject] IDBRequest getAll(optional any query, optional [EnforceRange] unsigned long count); [NewObject] IDBRequest getAllKeys(optional any query, optional [EnforceRange] unsigned long count); [NewObject] IDBRequest count(optional any query); [NewObject] IDBRequest openCursor(optional any query, optional IDBCursorDirection direction = "next"); [NewObject] IDBRequest openKeyCursor(optional any query, optional IDBCursorDirection direction = "next"); IDBIndex index(DOMString name); [NewObject] IDBIndex createIndex(DOMString name, (DOMString or sequenceIDBIndex) keyPath, optional IDBIndexParameters options); void deleteIndex(DOMString name); }; dictionary IDBIndexParameters { boolean unique = false; boolean multiEntry = false; };
该对象是用来进行 Index 索引的操作对象,里面也会存在 get 和 getAll 等方法。详细内容如下:
[Exposed=(Window,Worker)] interface IDBIndex { attribute DOMString name; [SameObject] readonly attribute IDBObjectStore objectStore; readonly attribute any keyPath; readonly attribute boolean multiEntry; readonly attribute boolean unique; [NewObject] IDBRequest get(any query); [NewObject] IDBRequest getKey(any query); [NewObject] IDBRequest getAll(optional any query, optional [EnforceRange] unsigned long count); [NewObject] IDBRequest getAllKeys(optional any query, optional [EnforceRange] unsigned long count); [NewObject] IDBRequest count(optional any query); [NewObject] IDBRequest openCursor(optional any query, optional IDBCursorDirection direction = "next"); [NewObject] IDBRequest openKeyCursor(optional any query, optional IDBCursorDirection direction = "next"); };
参考:
idb 开源库,微型代码库
treo 开源库
dexie.js 开源库
indexeddb
原生概念 IndexedDB
也欢迎大家关注我的公众号:前端小吉米 获得一手的技术文章以及未来技术的发展内容。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/95152.html
摘要:离线存储数据的建议对寻址资源,使用这是的一部分。在到达储量限制之前,两种存储机制都会一直进行存储。则没有对存储量做出限制,只是在之后会弹出提醒。是异步的基于回调函数,但它同样不支持。也是异步的基于回调函数,在和中可以工作虽然使用的是同步。 拖拖拉拉好久,终于把个人博客整出来了。鸣谢 @pinggod。 厚着脸安利一下,地址是 http://www.wemlion.com/。欢迎访问,欢...
摘要:离线存储数据的建议对寻址资源,使用这是的一部分。在到达储量限制之前,两种存储机制都会一直进行存储。则没有对存储量做出限制,只是在之后会弹出提醒。是异步的基于回调函数,但它同样不支持。也是异步的基于回调函数,在和中可以工作虽然使用的是同步。 拖拖拉拉好久,终于把个人博客整出来了。鸣谢 @pinggod。 厚着脸安利一下,地址是 http://www.wemlion.com/。欢迎访问,欢...
摘要:原文地址译文出自我的个人博客概述在本文中,您将学习如何使用和创建离线优先数据驱动的渐进式应用程序。在离线的情况下也可以使用后台同步功能将应用程序与服务器同步。保存数据到中现在我们保存数据到刚创建的数据库中的对象中。 原文地址:Build an offline-first, data-driven PWA译文出自:我的个人博客 概述 在本文中,您将学习如何使用 Workbox 和 In...
摘要:是对标准的第五次修订。新特性语义特性赋予网页更好的意义和结构文件类型声明仅有一型。新的属性用于与用于用于。索引数据库从本质上说,允许用户在浏览器中保存大量的数据。 HTML5 是对 HTML 标准的第五次修订。其主要的目标是将互联网语义化,以便更好地被人类和机器阅读,并同时提供更好地支持各种媒体的嵌入。HTML5 的语法是向后兼容的。现在国内普遍说的 H5 是包括了 CSS3,Java...
阅读 1833·2021-11-11 16:54
阅读 2059·2019-08-30 15:56
阅读 2366·2019-08-30 15:44
阅读 1286·2019-08-30 15:43
阅读 1857·2019-08-30 11:07
阅读 815·2019-08-29 17:11
阅读 1466·2019-08-29 15:23
阅读 3008·2019-08-29 13:01