随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据储存在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。
现有的浏览器数据储存方案,都不适合储存大量数据:Cookie 的大小不超过 4KB,且每次请求都会发送回服务器;LocalStorage 在 2.5MB 到 10MB 之间(各家浏览器不同),而且不提供搜索功能,不能建立自定义的索引。所以,需要一种新的解决方案,这就是 IndexedDB 诞生的背景。
通俗地说,IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
对比 | cookie | localStorage | sessionStorage | indexedDB |
---|---|---|---|---|
存储大小 | 4kb | 5M | 5M | 很多于 250MB,甚至没有上限 |
与服务器端通讯 | 每次都会携带在HTTP头中,若是使用cookie保存过多数据会带来性能问题 | 仅在客户端(即浏览器)中保存,不参与和服务器的通讯 | ||
生命周期 | 通常由服务器生成,可设置失效时间。若是在浏览器端生成Cookie,默认是关闭浏览器后失效 | 除非被清除,不然永久保存 | 仅在当前会话下有效,关闭页面或浏览器后被清除 | 除非被清除,不然永久保存 |
使用场景 | 判断用户是否登陆 | 存储一些内容稳定的资源。好比图片内容丰富的电商网站会用它来存储 Base64 格式的图片字符串 | 存储一些当前会话的信息,好比微博的 sessionStorage就主要是存储你本次会话的浏览足迹 | 和 localStorage 用途相似: 1. 存储量会更大 2. localStorage使用简单字符串键值对在本地存储数据,而indexedDB能够存储任意类型的值(适合键值对较多的数据,若是使用 localStorage 存储每次都要写入,写出须要字符串化和对象化) 复制代码 |
目前,Chrome 27+、Firefox 21+、Opera 15+和IE 10+支持这个API,但是Safari完全不支持。
下面的代码用来检查浏览器是否支持这个API。
if("indexedDB" in window) {
// 支持
} else {
// 不支持
}
IndexedDB 具有以下特点。
(1)键值对储存。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以“键值对”的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
(2)异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
(3)支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
(4)同源限制。 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
(5)储存空间大。 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
(6)支持二进制储存。 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
IndexedDB 是一个比较复杂的 API,涉及不少概念。它把不同的实体,抽象成一个个对象接口。学习这个 API,就是学习它的各种对象接口。
- 数据库:IDBDatabase 对象
- 对象仓库:IDBObjectStore 对象
- 索引: IDBIndex 对象
- 事务: IDBTransaction 对象
- 操作请求:IDBRequest 对象
- 指针: IDBCursor 对象
- 主键集合:IDBKeyRange 对象
下面是一些主要的概念。
(1)数据库
数据库是一系列相关数据的容器。每个域名(严格的说,是协议 + 域名 + 端口)都可以新建任意多个数据库。
IndexedDB 数据库有版本的概念。同一个时刻,只能有一个版本的数据库存在。如果要修改数据库结构(新增或删除表、索引或者主键),只能通过升级数据库版本完成。
(2)对象仓库
每个数据库包含若干个对象仓库(object store)。它类似于关系型数据库的表格。
(3)数据记录
对象仓库保存的是数据记录。每条记录类似于关系型数据库的行,但是只有主键和数据体两部分。主键用来建立默认的索引,必须是不同的,否则会报错。主键可以是数据记录里面的一个属性,也可以指定为一个递增的整数编号。
{ id: 1, value: '对应的值' }
上面的对象中,id
属性可以当作主键。
数据体可以是任意数据类型,不限于对象。
(4)索引
为了加速数据的检索,可以在对象仓库里面,为不同的属性建立索引。
(5)事务
数据记录的读写和删改,都要通过事务完成。事务对象提供error
、abort
和complete
三个事件,用来监听操作结果。
用例代码
<template>
<div>
<el-input v-model.trim="databaseName" :placeholder="`请输入数据库名称`" />
<el-input v-model.trim="tableName" :placeholder="`请输入表名称`" />
<el-input v-model.trim="index" :placeholder="`请输入字段名`" />
<el-button type="primary" @click="btn1()">创建数据库</el-button>
<hr>
<el-input v-model.trim="value" :placeholder="`请输入值`" />
<el-button type="success" @click="btn2()">添加数据</el-button>
<hr>
<el-input v-model.trim="keyPathValue" :placeholder="`请输入主键值`" />
<el-input v-model.trim="newValue" :placeholder="`请输入修改值`" />
<el-button type="warning" @click="btn3()">修改数据</el-button>
<hr>
<el-button type="info" @click="btn4()">读取数据</el-button>
<template v-if="tableData.length">
<el-button type="danger" @click="btn5">删除全部</el-button>
<el-table :data="tableData">
<el-table-column :prop="keyPath" :label="keyPath" />
<el-table-column :prop="index" :label="index" />
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="danger" @click.stop="btn6(scope.row[keyPath])">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
</div>
</template>
<script>
export default {
data() {
return {
databaseName: '',
tableName: '',
keyPath: 'id',
keyPathValue: '',
index: '',
value: '',
newValue: '',
tableData: [],
}
},
methods: {
// 用例----------------------------------------
// 创建
btn1() {
let databaseName = this.databaseName;
let version = this.version;
let tableName = this.tableName;
let keyPath = this.keyPath;
let indexs = [
[this.index, this.index, { unique: false }],
];//如需定义多格字段,就多几个数组
// 创建表
this.creatDatabaseTable({
databaseName, version,
tableName,//定义表名
keyPath,//定义主键
indexs,// 定义索引字段
});
},
// 添加
btn2() {
let databaseName = this.databaseName;
let version = this.version;
let tableName = this.tableName;
let keyPath = this.keyPath;
let index = this.index;
this.addData({
databaseName, version, tableName,
data: {
[keyPath]: '********'.replace(/\*/g, () => Math.round(Math.random() * 15).toString(16)),//随机id
[index]: this.value,
},
onsuccess: d => { console.log(`onsuccess`, d); },
onerror: d => { console.log(`onerror`, d); },
})
},
// 修改
btn3() {
let databaseName = this.databaseName;
let version = this.version;
let tableName = this.tableName;
let keyPath = this.keyPath;
let index = this.index;
this.updateData({
databaseName, version, tableName,
data: {
[keyPath]: this.keyPathValue,
[index]: this.newValue,
}
});
this.btn4();//刷新数据
},
// 读取
btn4() {
let databaseName = this.databaseName;
let version = this.version;
let tableName = this.tableName;
this.readData({
databaseName, version, tableName,
onsuccess: ({ data }) => { this.tableData = data },
})
},
// 删除全部
btn5() {
let databaseName = this.databaseName;
let version = this.version;
let tableName = this.tableName;
this.delAllData({ databaseName, version, tableName, });
this.btn4();//刷新数据
},
// 删除
btn6(id) {
let databaseName = this.databaseName;
let version = this.version;
let tableName = this.tableName;
this.delData({
databaseName, version, tableName,
data: id,//需要删除的数据主键
onsuccess: d => {
this.btn4();//刷新数据
},
onerror: d => { console.log(`onerror`, d); },
})
},
// indexedDB----------------------------------------
// 1、创建or打开客户端数据库
createDatabase({ databaseName, version = 1, onupgradeneeded, onsuccess, onerror } = {}) {
let request = window.indexedDB.open(databaseName, version);
request.onupgradeneeded = onupgradeneeded;
request.onsuccess = onsuccess;
request.onerror = onerror;
},
getDatabase(obj) { return this.createDatabase(obj); },//获取数据库
// 2、创建表
creatDatabaseTable({ databaseName, version, tableName, keyPath, indexs, onupgradeneeded } = {}) {
this.getDatabase({
databaseName, version,
onupgradeneeded: d => {
let database = d.target.result;
if (!database.objectStoreNames.contains(tableName)) {
//createObjectStore只能在onupgradeneeded里面执行
let objectStore = database.createObjectStore(tableName, { keyPath });
(indexs || []).forEach(v => objectStore.createIndex(...v));
onupgradeneeded && onupgradeneeded({ event: d, objectStore });
}
},
})
},
// 3、添加数据or修改数据or删除数据
addData({ databaseName, version, tableName, data, onsuccess, onerror, triggerName = 'add' } = {}) {
this.getDatabase({
databaseName, version,
onsuccess: d => {
let database = d.target.result;
let request = database.transaction(tableName, 'readwrite').objectStore(tableName)[triggerName](data);
request.onsuccess = onsuccess;
request.onerror = onerror;
},
})
},
// 4、修改数据
updateData(obj) { this.addData({ ...obj, triggerName: 'put' }) },
// 5、删除数据
delData(obj) { this.addData({ ...obj, triggerName: 'delete' }) },
delAllData({ databaseName, version, tableName } = {}) {
this.getDatabase({
databaseName, version,
onsuccess: d => {
let objectStore = d.target.result.transaction(tableName, 'readwrite').objectStore(tableName);
objectStore.clear();
},
})
},
// 6、读取数据
readData({ databaseName, version, tableName, onsuccess } = {}) {
this.getDatabase({
databaseName, version,
onsuccess: d => {
let database = d.target.result;
if (database.objectStoreNames.contains(tableName)) {
let objectStore = database.transaction(tableName).objectStore(tableName);
let data = [];
objectStore.openCursor().onsuccess = event => {
let cursor = event.target.result;
if (cursor) {
data.push(cursor.value); cursor.continue();
} else {
onsuccess && onsuccess({ event, data });
// console.log('没有更多数据了!');
}
};
} else onsuccess && onsuccess({ msg: '表格不存在!', data: [] });
},
})
},
// ----------------------------------------
}
};
</script>