IndexedDB_Web 离线数据库

IndexedDB_Web 离线数据库

本文会从头剖析一下 IndexedDB 在前端里面的应用的发展。

indexedDB 目前在前端慢慢得到普及和应用。它正朝着前端离线数据库技术的步伐前进。以前一开始是 manifest、localStorage、cookie 再到 webSQL,现在 indexedDB 逐渐被各大浏览器认可。我们也可以针对它来进行技术上创新的开发。比如,现在小视频非常流行,那么我们可以在用户观看时,通过 cacheStorage 缓存,然后利用 WebRTC 技术实现 P2P 分发的控制,不过需要注意,一定要合理利用大小,不然后果真的很严重。

indexedDB 的整体架构,是由一系列单独的概念串联而成,全部概念如下列表。一眼看去会发现没有任何逻辑,不过,这里我顺手画了一幅逻辑图,中间会根据 函数 的调用而相互串联起来

  • IDBRequest
  • IDBFactory
  • IDBDatabase
  • IDBObjectStore
  • IDBIndex
  • IDBKeyRange
  • IDBCursor
  • IDBTransaction

整体逻辑图如下:

image

本文讲解流程:

下文主要介绍了 indexedDB 的基本概念,以及在实际应用中的实操代码。

  • indexedDB 基础概念。在 indexedDB 里面会根据索引 index 来进行整体数据结构的划分。
  • indexedDB 数据库的更新是一个非常蛋疼的事情,因为,Web 的灵活性,你既需要做好向上版本的更新,也需要完善向下版本的容错性。
  • indexedDB 高效索引机制,在内部,indexedDB 已经提供了 index、cursor等高效的索引机制,推荐不要直接将所有数据都取回来,再进行筛选,而是直接利用 cursor 进行。
  • 最后推荐几个常用库

离线存储

IndexedDB 可以存储非常多的数据,比如 Object,files,blobs 等,里面的存储结构是根据 Database 来进行存储的。每个 DB 里面可以有不同的 object stores。具体结构如下图:

image

并且,我们可以给 key 设定相关特定的值,然后在索引的时候,可以直接通过 key 得到具体的内容。使用 IndexDB 需要注意,其遵循的是同域原则。

indexDB 基本概念

在 indexDB 中,有几个基本的操作对象:

  • Database: 通过 open 方法直接打开,可以得到一个实例的 DB。每个页面可以创建多个 DB,不过一般都是一个。
idb.open(name, version, upgradeCallback)
  • Object store: 这个就是 DB 里面具体存储的对象。这个可以对应于 SQL 里面的 table 内容。其存储的结构为:

image

  • index: 有点类似于外链,它本身是一种 Object store,主要是用来在本体的 store 中,索引另外 object store 里面的数据。需要区别的是,key 和 index 是不一样的。可以参考: index DEMO,mdn index。如下图表示:

image

如下 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<DOMString>)? keyPath = null;boolean autoIncrement = false;
};
  • keyPath: 用来设置主键的 key,具体区别可以参考下面的 keyPath 和 generator 的区别。
  • autoIncrement: 是否使用自增 key 的特性。

设置索引 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<DOMString>) 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 的版本呢?

  1. 该表数据库里面的 keyPath 时。
  2. 你需要重新设计数据库表结构时,比如新增 index
# 版本 1 的 DB 设计,有一个主键 idindex-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无逐出
IE10无逐出

参考:

存储上限值 浏览器内核存储上限值处理

数据索引方式

在数据库中除了基本的 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.valuecursor.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);}
};

在游标中,还提供给了一个 updatedelete 方法,我们可以用它来进行数据的更新操作,否则的话就直接使用 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 范围,即,索引 在 villainhrjimmyVV 之间的数据集合。

# 都包括 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 会根据监听 errorabort,以及 complete 三个事件来完成整个事务的流程管理,参考[代码2]。

[代码1][NewObject] IDBTransaction transaction((DOMString or sequence<DOMString>) 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 中同时加上 keyPathautoIncrement 属性。该 key 的范围是 [1- ],参考 keygenerator key 的大小

db.createObjectStore('table1', {keyPath: 'id', autoIncrement: true});

推荐

阅读推荐

indexedDB W3C 文档 indexedDB 入门 MDN indexedDB 入门

好用库推荐

idb: 一个 promise 的 DB 库

Indexed Appendix

  • IndexedDB 数据库使用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 generatorkey 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<DOMString>) 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<DOMString>)? 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 sequence<DOMString>) keyPath,optional IDBIndexParameters options);void deleteIndex(DOMString name);
};dictionary IDBIndexParameters {boolean unique = false;boolean multiEntry = false;
};

IDBIndex

该对象是用来进行 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/536094.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于 Docker 的微服务架构

基于 Docker 的微服务架构-分布式企业级实践前言Microservice 和 Docker服务发现模式客户端发现模式Netflix-Eureka 服务端发现模式ConsulEtcdZookeeper 服务注册自注册模式 Self-registration pattern第三方注册模式 Third party registration pattern小结一 服务间的 IPC 机制…

funcode游戏实训,java及C/C++,网上整理

软件&#xff0c;常见错误都有。 所有资源可到公众号获取(源码也是)&#xff0c;不再直接分享

swing皮肤包 substance

分享一下swing皮肤包substance 资源可到公众号获取

基于Android的聊天软件,Socket即时通信,实现用户在线聊天

基于Android的聊天软件&#xff0c;Socket即时通信&#xff0c;单聊&#xff0c;聊天室&#xff0c;可自行扩展功能&#xff0c;完善细节。 【实例功能】 1.运行程序&#xff0c;登录界面, 注册账号功能 2.进入主界面&#xff0c;有通讯录, 个人信息。 3.点击好友会话框&#…

Go实现简单的RESTful_API

Go实现简单的RESTful_API 何为RESTful API A RESTful API is an application program interface (API) that uses HTTP requests to GET, PUT, POST and DELETE data. A RESTful API – also referred to as a RESTful web service – is based on representational state t…

Weave Scope安装

Weave Scope安装 首先确保已经安装docker 实时了解Docker容器状态 查看容器基础设施的概况&#xff0c;或者专注于一个特殊的微服务。从而轻松发现并纠正问题&#xff0c;确保你的容器化应用的稳定与性能。 内部细节与深度链接 查看容器的指标、标签和元数据。 在一个可扩展…

MYSQL导入数据出现ERROR 1049 (42000): Unknown database 'n??;'

MYSQL导入数据出现ERROR 1049 (42000): Unknown database ‘n??;’ 背景 我需要将一个csv表中的数据导入数据库创建好的表中&#xff0c;产生的问题如下&#xff1a; 原因寻找 因为我并没有使用’n??;的数据库&#xff0c;所以很好奇这个错怎么来的&#xff0c;尝试如下…

SVG入门理解

SVG入门SVG 元素简单的形状rectcircleellipselinetextpath SVG 样式分层和绘图顺序透明度 SVG入门 当我们用SVG生成和操作一些炫酷的效果时,D3是最佳选择。使用div和其他原生HTML元素绘图也是可以的&#xff0c;但是太笨重&#xff0c;而且会受到不同浏览器的限制&#xff0c…

【哈佛幸福课笔记】【1】

【哈佛幸福课笔记】【1】 ​ ​ 一个月的时间看完了哈佛幸福课&#xff0c;正如Tal所说课程的结束才是学习的开始。对于课程观点的思考&#xff0c;对于个人习惯的培养&#xff0c;对于思维模式的转变还需要花大量的时间去练习。这个系列的帖子将作为我个人的读书笔记&#x…

RAID阵列基础知识

RAID阵列基础知识 独立硬盘冗余阵列 &#xff08;RAID, Redundant Array of Independent Disks&#xff09;,旧称廉价磁盘冗余阵列&#xff08;Redundant Array of Inexpensive Disks&#xff09;&#xff0c;简称磁盘阵列。 RAID的种类 这里我们只介绍比较常用的RAID类型&am…

timeshift备份你的Linux系统

timeshift备份你的Linux系统 安装 打开终端&#xff08;ctrl alt T&#xff09;并逐个执行以下命令 sudo apt-add-repository -y ppa:teejee2008/ppa sudo apt-get update sudo apt-get install timeshift 创建 点击Create按钮 默认不能备份用户下的文件&#xff0c;所以…

Go语言vscode环境配置

Go语言vscode环境配置 此教程在GO已经安装成功的前提下。 安装vscode扩展 在vscode扩展里面搜索go&#xff0c;然后下载扩展。 安装go 插件 在$GOPATH目录下创建bin,pkg,src切换到$GOPATH/bin目录下&#xff0c;打开终端输入以下命令&#xff0c;不需要翻墙&#xff1a; go…

消息队列-Message Queue

消息队列-Message Queue 目前随着互联网的普及以及上网用户的增多&#xff0c;拥有一套 安全、稳定、低耦合、高性能的内部通信工具尤为重要。 什么是消息队列&#xff1f; 消息队列&#xff08;英语&#xff1a;Message queue&#xff09;是一种进程间通信或同一进程的不同线…

Angular 第一章 开始

第一章 开始 用 JavaScript 开发应用程序是一个很大的挑战。由于它的延展性和缺少类型检查,在 JavaScript 中构建一个适当大小的应用程序是很困难的。除此之外,我们对所有类型的处理都使用JavaScript,例如用户界面(UI),操作、客户端-服务器交互和业务处理/验证。因此,我们…

Typescript实现单例之父类调用子类

Typescript实现单例之父类调用子类 设计要求 在程序中,需要一个对象可以全局使用,并且只有一个实例Breakpoint 类是一个可以被继承的类,然后子类必须实现 updateView函数updateView 这个函数可以被自动调用,当窗口发生变化的时候构思 UML 图 Layout 是一个单例类,也就是全局只…

Angular性能优化之脏检测

Angular性能优化之脏检测 当我们在使用 Angular 框架搭建项目时,随着组件越来越多,页面也来越复杂,性能会越来越低,主要表现在 CPU 使用率 很高。所以我们要对项目做一定的优化。 Angular脏检查(Change Detection)机制 Angular 的脏检测主要是指 zone.js,这是一个开源的…

linux安装zsh终端

linux安装zsh终端 ZSH 已经被收录到了 Ubuntu 18.04 LTS 的官方软件包存储库中了 sudo apt install zshZSH Shell 安装好之后&#xff0c;可以使用如下命令查看其版本&#xff1a; zsh --version取代bash&#xff0c;设为默认shell sudo usermod -s /bin/zsh username也可以…

共享图片方案

共享图片方案 安装chrome插件 极简图床安装&#xff0c;链接地址 插件使用 使用阿里云 OSS 存储图片 阿里云 OSS 提供了安全、低成本、高可靠的云存储服务&#xff0c;极简图床针对阿里云 OSS 做了整合&#xff0c;通过简单的设置&#xff0c;即可方便地将图片上传到阿里…

javascript复制到黏贴板之完美兼容

javascript复制到黏贴板之完美兼容 很多时候我们需要给用户方便,提供一键复制的功能,但是在实现的过程中遇到各式各样的坑。 原生解决方案 document.execCommand()方法 MDN上的定义&#xff1a; which allows one to run commands to manipulate the contents of the edita…

制作windows启动盘-大于4GB镜像

制作windows启动盘-大于4GB镜像 制作一个 Windows 安装 U 盘是很容易的&#xff0c;使用 UltraISO 这样的刻录工具量产一个 iso 镜像文件到 U 盘即可。然而随着 Windows 10 版本号的提升&#xff0c;镜像变得越来越大&#xff0c;终于 FAT32 文件系统不再能够容纳得下安装镜像…