在知乎和我在平常工作中,常常会看到一个问题:

前端现在还火吗?

这个我只想说:

隔岸观火的人永远无法明白起火的原因,只有置身风暴,才能找到风眼之所在 ——『秦时明月』

你 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});

增删数据

在 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 上,已经有现成的办法,我们直接使用即可。

参考:

存储上限值

基本限制为:

浏览器

限制

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 方法遍历得到的数据。

参考:

游标索引

所谓的游标,大家心里应该可以有一个初步的印象,就像我们物理尺子上的那个东西,可以自由的移动,来标识指向的对象内容。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});

推荐

阅读推荐

好用库推荐

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 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 的范围内容。

参考:

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) {...};

参考:

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 和插入数据。参考下面内容。

参考:

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 sequence) 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");

};

参考:

也欢迎大家关注我的公众号:前端小吉米 获得一手的技术文章以及未来技术的发展内容。

java indexeddb_IndexedDB 打造靠谱 Web 离线数据库相关推荐

  1. IndexedDB 打造靠谱 Web 离线数据库

    在知乎和我在平常工作中,常常会看到一个问题: 前端现在还火吗? 这个我只想说: 隔岸观火的人永远无法明白起火的原因,只有置身风暴,才能找到风眼之所在 --『秦时明月』 你 TM 看都不看前端现在的发展 ...

  2. java web 数据库操作_Java Web----Java Web的数据库操作(二)

    Java Web的数据库操作 三.JDBC操作数据库 上一篇介绍了JDBC API,之后就可以通过API来操作数据库,实现对数据库的CRUD操作了. 下面仅以示例 的方式对数据库操作进行说明 1. 添 ...

  3. java web 数据库操作_Java Web----Java Web的数据库操作(三)

    Java Web的数据库操作 前面介绍了JDBC技术和JDBC API及API的使用示例,下面详细介绍JDBC在Web中的应用. 四.JDBC在Java Web中的应用 通常情况下,Web程序操作数据 ...

  4. JSP详细篇——Java Web的数据库操作

    Java Web的数据库操作 JDBC技术 JDBC简介 JDBC是Java程序曹祖数据库的API,也是Java程序与数据库交互的一门技术.JDBC是java操作数据库的规范,由一组用Java语言编写 ...

  5. java计算机毕业设计全域旅游Web平台源代码+数据库+系统+lw文档

    java计算机毕业设计全域旅游Web平台源代码+数据库+系统+lw文档 java计算机毕业设计全域旅游Web平台源代码+数据库+系统+lw文档 本源码技术栈: 项目架构:B/S架构 开发语言:Java ...

  6. 基于JAVA学习和分享做菜web系统计算机毕业设计源码+系统+数据库+lw文档+部署

    基于JAVA学习和分享做菜web系统计算机毕业设计源码+系统+数据库+lw文档+部署 基于JAVA学习和分享做菜web系统计算机毕业设计源码+系统+数据库+lw文档+部署 本源码技术栈: 项目架构:B ...

  7. java web 导入数据库_关于JAVA、 JAVA Web项目导入数据库驱动包的问题

    导入jdbc驱动程序包其实有很多种方法,但是不同的导包方式有不同的含义, 1.给Tomcat导包(表示服务器可能要用到数据库,例如数据源),如果是MyEclipse集成Tomcat,显然是要用&quo ...

  8. java 婚恋交友网站Myeclipse开发mysql数据库web结构jsp编程计算机网页项目

    一.源码特点     java 婚恋交友网站是一套完善的java web信息管理系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发.开发环境为 T ...

  9. java计算机毕业设计ssm基于web的教学资源管理系统01jkz(附源码、数据库)

    java计算机毕业设计ssm基于web的教学资源管理系统01jkz(附源码.数据库) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysql + HBuilderX(Webstor ...

最新文章

  1. 3D视觉工坊——一个有趣有料的星球
  2. 【转】浅谈php://filter的妙用
  3. boost::mpl模块实现next相关的测试程序
  4. java调度:(六)quarts_cron表达式
  5. 检测浏览器是否支持WebSocket
  6. Spring容器启动时出现Failed to read schema document错误
  7. 根据某个字段判断是否添加条件
  8. 微星msi B450M+i5-8500+1060成功黑苹果
  9. java中UUID类生成32位随机数(附加 6 位随机数)
  10. 待业在家的6个月,我靠淘宝月入百万:你看不起的行业,往往很赚钱
  11. Python Twisted 简介
  12. C++十一月月末总结
  13. PD 关于tso 分配源代码分析
  14. 小米9开发版已开启Android,小米9迎来最后一个基于安卓9的系统,即将启动安卓q开发版内测...
  15. 华为计算机的隐藏应用程序,华为平板5怎么隐藏软件 荣耀5平板怎么隐藏应用程序?...
  16. 算法 | 虚树学习笔记
  17. 传统企业如何搭上互联网+的大船
  18. VUE项目中如何实现表格数据的懒加载
  19. ios和安卓兼容问题(font-weight,new Date)
  20. 输入您的密码来连接到_【对讲机的那点事】如何为您的数字对讲机热点配置安全密码...

热门文章

  1. 学习Linux命令(6)
  2. Java中级(一)异常处理、I/O、集合框架
  3. 以小见大——那些基于 protobuf 的五花八门的 RPC(4)
  4. 组织或项目外部影响因素分析
  5. java中二叉树中第k大的数,寻找第k大的数
  6. 为什么要做微信小程序
  7. 用python写投票程序_大话python最终篇,web.py 开发的投票程序demo
  8. TiDB ——TiKV
  9. php进销存 手机版_银鱼进销存app下载-银鱼进销存手机版 v1.4.6
  10. 利用语义分割(FCN)区分两种有文字和无文字区域