浏览器中的 IndexedDB

作用

数据存储在客户端的方法:

  • Cookie:太小 4k
  • Storage:10M 够,但不能搜索和自定义索引
  • WebSQL:W3C 已不维护,更推荐 IndexDB。
  • IndexDB:本期主题

简介

浏览器提供的本地数据库,可以被网页脚本创建和操作。提供查找接口,还能建立索引。更接近 NoSql

IndexedDB 特点

  1. 键值对储存。所有类型的数据都可以直接存入,包括 JavaScript 对象。在库中,数据以”键值对”的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复。

  2. 异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,LocalStorage 的操作是同步的。异步设计是为了防止大量数据的读写,做到了很好的优化。

  3. 支持事务。 IndexedDB 支持事务(transaction),只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。

  4. 同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。

  5. 储存空间大 IndexedDB 的储存空间比 LocalStorage 大很多,不少于 250MB,甚至没有上限【不同的浏览器表现不同】。

  6. 支持二进制储存。 IndexedDB 不仅可存字符串,还可以存二进制数据(ArrayBuffer 对象和 Blob 对象)。

测试案例

下面是一段对 IndexedDB 的简单应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>IndexedDb</title>
</head>
<body>
<button onclick="add()">添加一条</button>
<button onclick="getTables()">获取内容</button>
<button onclick="upgrade()">修改一条</button>
<button onclick="del()">删除一条</button>
<script>
class IndexedDbLib {
constructor(dbname, tables, version) {
// 版本号只能是正整数,且不可减少,如果版本号增加触发升级事件,如果版本号减少触发error事件
// 创建了数据库对象
this.db = window.indexedDB.open(dbname, version);
this.tables = tables;
this.#_init();
}

#_init() {
// bind events ---
// 数据库对象的 error 事件
this.db.onerror = (event) => {
console.log("数据库打开报错");
};
// 数据库对象的 success 事件
this.db.onsuccess = (event) => {
console.log(event.target.result);
console.log("数据库打开成功");
};

// 数据库升级事件,根据版本号变化,没有该数据库时相当于 0
this.db.onupgradeneeded = (event) => {
console.log("数据库创建/升级");

// 数据库新建成功以后,开始添加表,建表操作只能在 数据库被创建时执行
Object.keys(this.tables).forEach((keyName) => {
let objectStore;
if (this.db.result.objectStoreNames.contains(keyName)) {
return;
}
const { keyPath, columns } = this.tables[keyName];
objectStore = this.db.result.createObjectStore(keyName, {
keyPath,
});

columns.forEach((col) => {
const { key, options } = col;
// 键值名 ,索引名, {unique 是否唯一}
objectStore.createIndex(key, key, options);
});
});
};
}

addItems(tables, rowData) {
// 创建个事务,模式为 “读写” (可以对多个表发起事务,操作时只能针对事务里的单个表操作)
const request = this.db.result.transaction(tables, "readwrite");
// 为两个表都添加
tables.forEach((item) => {
request.objectStore(item).add(rowData);
});
request.oncomplete = (event) => {
console.log("数据写入成功");
};
request.onerror = (event) => {
console.log("数据写入失败");
};
}

deleteItems(table, id) {
const request = db
.transaction([table], "readwrite")
.objectStore(table)
.delete(id);

request.onsuccess = (event) => {
console.log("数据删除成功");
};
}

updateItems(table, rowData) {
const request = db
.transaction([table], "readwrite")
.objectStore(table)
.put(rowData);

request.onsuccess = (event) => {
console.log("数据更新成功");
};

request.onerror = (event) => {
console.log("数据更新失败");
};
}

getItems(tables, id) {
return new Promise((resolve) => {
const result = {};

// get All
if (!id) {
const transaction = this.db.result.transaction(tables);
tables.forEach((item) => {
const request = transaction.objectStore(item);
request.openCursor().onsuccess = (event) => {
const cursor = event.target.result;

if (cursor) {
console.log("Id: " + cursor.key);
console.log("Name: " + cursor.value.name);
console.log("Age: " + cursor.value.age);
console.log("Email: " + cursor.value.email);
result[item] = result[item] || [];
result[item].push(cursor.value);
cursor.continue();
} else {
console.log("没有更多数据了!");
if (Object.keys(result).length === tables.length) {
resolve(result);
}
}
};
});
return;
}

// get Item
const transaction = this.db.result.transaction(tables);
tables.forEach((item) => {
// 获取主键值
const request = transaction.objectStore(item).get(id);

request.onerror = (event) => {
console.log("事务失败");
};

request.onsuccess = (event) => {
if (request.result) {
console.log("Name: " + request.result.name);
console.log("Age: " + request.result.age);
console.log("Email: " + request.result.email);
result[item] = request.result;

if (Object.keys(result).length === tables.length) {
resolve(result);
}
} else {
console.log("未获得数据记录");
}
};
});
});
}
}

const dbClass = new IndexedDbLib(
"dbname",
{
person: {
keyPath: "id",
columns: [
{ key: "email", options: { unique: false } },
{ key: "name", options: { unique: true } },
{ key: "age", options: { unique: true } },
],
},
testTable: {
keyPath: "id",
columns: [
{ key: "email", options: { unique: false } },
{ key: "name", options: { unique: true } },
{ key: "age", options: { unique: true } },
],
},
},
1
);

const add = () => {
dbClass.addItems(["person", "testTable"], {
id: 2,
name: "张三",
age: 24,
email: "zhangsan@example.com",
});
};

const getTables = () => {
dbClass.getItems(["person", "testTable"]).then(console.log);
};

const upgrade = () => {
dbClass.updateItems("person", {
id: 1,
name: "李四",
age: 35,
email: "lisi@example.com",
});
};

const del = () => {
dbClass.deleteItems("person", 1);
};
</script>
</body>
</html>

IndexedDB 相关的封装库

  • localForage:一个简单的 Polyfill,提供了简单的客户端数据存储的值语法。它在后台使用 IndexedDB,并在不支持 IndexedDB 的浏览器中回退到 WebSQL 或 localStorage。
  • Dexie.js:IndexedDB 的包装,通过简单的语法,可以更快地进行代码开发。
  • ZangoDB:类似 MongoDB 的 IndexedDB 接口,支持 MongoDB 的大多数熟悉的过滤、投影、排序、更新和聚合功能。
  • JsStore:一个带有 SQL 语法的 IndexedDB 包装器。
  • MiniMongo:由 localstorage 支持的客户端内存中的 mongodb,通过 http 进行服务器同步。MeteorJS 使用 MiniMongo。
  • PouchDB:使用 IndexedDB 在浏览器中实现 CouchDB 的客户端。
  • idb:一个微小的(〜1.15k)库,大多 API 与 IndexedDB 类似,但做了一些小的改进,让数据库的可用性得到了大大的提升。
  • idb-keyval:使用 IndexedDB 实现的超级简单且小巧的(~600B)基于 Promise 的键值对存储。
  • sifrr-storage:一个非常小的(~2kB)基于 Promise 的客户端键值数据库。基于 IndexedDB、localStorage、WebSQL 和 Cookies 实现。它可以自动选择上述支持的数据库,并按照优先顺序使用。
  • lovefield:Lovefield 是一个用于 Web App 的关系型数据库,使用 JavaScript 编写,可以在不同的浏览器环境中运行,提供了类似 SQL 的 API,速度快、安全且易用。
作者

Huasun47

发布于

2019-12-30

更新于

2019-12-30

许可协议