说起 Web
缓存,我们自然就会想到 Cookie
,LocalStorage
和 SessionStorage
,却很少提及 IndexedDB
。
上面说到的常见缓存技术,简单来说:
4KB
。那么 IndexedDB 呢?下面我们开讲~
用户需要在本地存储大量的数据以满足离线缓存或者其他操作。并且可以按顺序检索,有效搜索值并可键值对存储,IndexedDB 应运而生。该规范提供了一个具体的 API
来执行高级键值数据管理。
在此之前还有一个类似数据库 Web SQL Database 的草案,但是在 2010-11-18 日宣布舍弃该草案。
那么,IndexedDB 具体能够帮助到我们什么呢?
上面也已经提及了,IndexedDB 存储数据特点:
存储的数据,除了可以存储字符串数据,还可以:
ArrayBuffer
对象和 Blob
对象。IndexedDB 不同于前面提及的几种同步
缓存,它是:
当然,IndexedDB 也跟上面提及他缓存一样:
作为一个本地存储的数据库,它友好地:
这个很重要。打个比方,你去银行取钱 ¥100,000
,银行从你余额 ¥100,001
的账号上抹掉了那么多。但是,银行最后却没有给到钱。你的账户上却是 ¥1
,那心态崩了啊。
IndexedDB 相对前面提及的缓存,其存储空间远远比它们大:
1/2
。我们可以通过 StorageManager.estimate() 来查看存储使用情况。这里我用 Snippets 展示。不熟悉使用的读者可以通过 运行 JavaScript 代码片段 进行了解。
可概括为一句话:IndexedDB 能够方便、安全且可靠地处理大量数据。
下面我们来实现一个列表增删查改的功能。
因为工作上使用 Angular
比较多,所以本文就用 Angular
进行展示。vue
和 react
同理。如果想通过纯 JavaScript
演示,推荐阅读HTMl5 indexedDB存储编辑和删除数据实例页面。
本案例实现的效果,如下图👇:
案例完成的功能有:
IndexedDB
并创建对象(表)及索引table
的数据IndexedDB
中的数据IndexedDB
中的数据IndexedDB
中的数据IndexedDB
中读取并展示在 当前选中
位置案例采用的 UI 框架是 Ant Design of Angular
代码即文档,详细代码和解析如下:
<!--src/app/pages/welcome/welcome.component.html-->
<!--table 组件-->
<nz-table #basicTable [nzData]="list" [nzShowPagination]="false">
<thead>
<tr>
<th>ID</th>
<th>添加时间</th>
<th>名称</th>
<th>操作 <button nz-button nzType="primary" (click)="addItem()">添加</button></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of basicTable.data">
<td>{{data.id}}</td>
<td>{{data.id | date:'yyyy-MM-dd HH:mm:ss'}}</td>
<td>{{data.name}}</td>
<td>
<a (click)="getItem(data.id)">选中</a>
<nz-divider nzType="vertical"></nz-divider>
<a (click)="editItem(data)">编辑</a>
<nz-divider nzType="vertical"></nz-divider>
<a style="color: #f00;" (click)="deleteItem(data.id)">删除</a>
</td>
</tr>
</tbody>
</nz-table>
<p style="margin-top: 36px; margin-bottom: 12px; text-align: center; color: #666;">当前选中</p>
<pre style="border: 1px solid #999; padding: 24px 12px; border-radius: 6px;">
{{selectedItem || '暂无选中'}}
</pre>
// src/app/pages/welcome/welcome.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-welcome',
templateUrl: './welcome.component.html',
styleUrls: ['./welcome.component.less']
})
export class WelcomeComponent implements OnInit {
// 列表数据
public list:any = [];
// 数据库名称
private readonly dbName: string = 'User-service';
private readonly storeName: string = 'UserInfo'
// 版本
private version: number = 1;
// 数据库结果
public db: any = null;
// 当跳记录
public selectedItem: any = null;
constructor() { }
ngOnDestroy() {
// 关闭组件后,关闭数据库
this.closeDb();
}
ngOnInit() {
this.connectDb();
}
// 错误函数统一处理
logError(msg: string) {
console.error(msg);
}
// 连接数据库
connectDb() {
// 打开数据库
var DBOpenRequest = window.indexedDB.open(this.dbName, this.version);
// 如果数据库打开失败
DBOpenRequest.onerror = (event) => {
this.logError('数据库打开失败');
};
// 数据库连接成功
DBOpenRequest.onsuccess = (event: any) => {
// 存储数据结果
this.db = DBOpenRequest.result;
// 获取列表数据
this.getList();
};
// ⚠️下面事情执行于:数据库首次创建版本,或者window.indexedDB.open传递的新版本(版本数值要比现在的高)
DBOpenRequest.onupgradeneeded = (event: any) => {
var db = event.target.result;
db.onerror = (event: any) => {
this.logError('数据库打开失败');
};
// 创建一个数据库存储对象(表)及其索引
var objectStore = db.createObjectStore(this.storeName, {
keyPath: 'id',
autoIncrement: true
});
// 定义存储对象的数据项
objectStore.createIndex('id', 'id', {
unique: true
});
objectStore.createIndex('name', 'name');
};
}
// 关闭数据库
closeDb() {
this.db.close();
}
// 删除数据库
deleteDb(): Promise<any> {
return new Promise<any> ((resolve, reject) => {
// 先关闭数据库
this.closeDb();
// 再删除数据库
let request = window.indexedDB.deleteDatabase(this.dbName);
request.onsuccess = (event: any) => {
this.db = null;
resolve(event);
}
request.onerror = reject;
})
}
// 获取列表数据
getList() {
// 打开对象存储,获得游标列表
let objectStore = this.db.transaction(this.storeName).objectStore(this.storeName);
const tempList:any = []; // 添加一个中转
objectStore.openCursor().onsuccess = (event: any) => {
let cursor = event.target.result;
if(cursor) {
tempList.push(cursor.value);
cursor.continue();
} else {
console.log('no data');
this.list = tempList;
}
}
}
// 读取一条数据
getItem(id: number) {
let objectStore = this.db.transaction([this.storeName], 'readwrite').objectStore(this.storeName);
let request = objectStore.get(id); // get 使用索引搜索,这里使用的是 id 这个索引
request.onsuccess = (event: any) => {
this.selectedItem = JSON.stringify(request.result);
}
}
// 添加项目
addItem() {
const item = {
id: (new Date()).valueOf(),
name: 'Jimmy - ' + (Math.random() * 100).toFixed(0)
}
let transaction = this.db.transaction([this.storeName], "readwrite");
// 打开已经存储的数据对象
var objectStore = transaction.objectStore(this.storeName);
// 添加到数据对象中
var objectStoreRequest = objectStore.add(item);
objectStoreRequest.onsuccess = (event: any) => {
this.getList()
};
}
// 删除项目
deleteItem(id: number) {
// 打开已经存储的数据对象
let objectStore = this.db.transaction([this.storeName], "readwrite").objectStore(this.storeName);
// 直接删除
let objectStoreRequest = objectStore.delete(id);
// 删除成功后
objectStoreRequest.onsuccess = () => {
this.getList();
};
}
// 编辑项目
editItem(item: any) {
const newItem = {
id: item.id,
name: item.name.slice(0, 10) + ' - edit ' + (new Date()).toLocaleString()
}
// 编辑数据
let transaction = this.db.transaction([this.storeName], "readwrite");
// 打开已经存储的数据对象
let objectStore = transaction.objectStore(this.storeName);
// 获取存储的对应键的存储对象
let objectStoreRequest = objectStore.get(item.id);
objectStoreRequest.onsuccess = (event: any) => {
// 更新数据库存储数据
objectStore.put(newItem);
this.getList();
}
}
}
断网之后,刷新页面,我们依旧得到相同的数据。通过 Application
> IndexedDB
路径查看, User-service
的存储数据不变。
自从 2015-0-08 起被 W3C 推荐使用以来,经过多年的发展,伴随着 IE 浏览器退出历史舞台,现代浏览器对 IndexedDB 支持情况甚是友好。
本文正在参加「金石计划 . 瓜分6万现金大奖」