0
点赞
收藏
分享

微信扫一扫

Web 中使用 IndexedDB 实现缓存

说起 ​​Web​​​ 缓存,我们自然就会想到 ​​Cookie​​​,​​LocalStorage​​​ 和 ​​SessionStorage​​​,却很少提及 ​​IndexedDB​​。

上面说到的常见缓存技术,简单来说:

  • Cookie 缓存的数据可跟服务端进行交互信息,但是大小不超过 ​​4KB​​。
  • LocalStorage 将信息字符串化后存储,大小一般几兆。是一种同步操作。永久缓存,除非手动删除。
  • SessionStorageLocalStorage 类似,但是关闭站点之后,缓存数据就会消失。

那么 IndexedDB 呢?下面我们开讲~

IndexedDB 是什么

用户需要在本地存储大量的数据以满足离线缓存或者其他操作。并且可以按顺序检索,有效搜索值并可键值对存储,IndexedDB 应运而生。该规范提供了一个具体的 ​​API​​ 来执行高级键值数据管理。

Web 中使用 IndexedDB 实现缓存_数据库

在此之前还有一个类似数据库 ​​Web SQL Database​​ 的草案,但是在 2010-11-18 日宣布舍弃该草案。

IndexedDB 能够解决什么

那么,IndexedDB 具体能够帮助到我们什么呢?

上面也已经提及了,IndexedDB 存储数据特点:

  • 键值对存储

存储的数据,除了可以存储字符串数据,还可以:

  • 支持二进制的存储。​​ArrayBuffer​​​ 对象和 ​​Blob​​ 对象。

IndexedDB 不同于前面提及的几种​​同步​​缓存,它是:

  • 异步操作。防止大量数据的读写,造成页面卡顿。

当然,IndexedDB 也跟上面提及他缓存一样:

  • 受到同源限制。保证数据的安全性。什么是同源限制,可参考​​浏览器的同源策略​​。

作为一个本地存储的数据库,它友好地:

  • 支持事务(transaction)。这意味着一系列操作步骤中,只要有一步失败,整个事务都会取消,数据库就会回滚到发生之前的状态,不存在只改写了一部分数据的情况。这个很赞👍

这个很重要。打个比方,你去银行取钱 ​​¥100,000​​​,银行从你余额 ​​¥100,001​​​ 的账号上抹掉了那么多。但是,银行最后却没有给到钱。你的账户上却是 ​​¥1​​ ,那心态崩了啊。

IndexedDB 相对前面提及的缓存,其存储空间远远比它们大:

  • 存储空间超大。具体是多大?这取决于你硬盘的大小。一般是你硬盘大小的 ​​1/2​​。

我们可以通过 ​​StorageManager.estimate()​​ 来查看存储使用情况。这里我用 Snippets 展示。不熟悉使用的读者可以通过 ​​运行 JavaScript 代码片段​​ 进行了解。

Web 中使用 IndexedDB 实现缓存_数据库_02

可概括为一句话:IndexedDB 能够方便、安全且可靠地处理大量数据

IndexedDB 实现案例

下面我们来实现一个列表增删查改的功能。

因为工作上使用 ​​Angular​​​ 比较多,所以本文就用 ​​Angular​​​ 进行展示。​​vue​​​ 和 ​​react​​​ 同理。如果想通过纯 ​​JavaScript​​ 演示,推荐阅读​​HTMl5 indexedDB存储编辑和删除数据实例页面​​。

本案例实现的效果,如下图👇:

Web 中使用 IndexedDB 实现缓存_数据库_03

案例完成的功能有:

  • 连接 ​​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​​ 的存储数据不变。

Web 中使用 IndexedDB 实现缓存_前端_04

IndexedDB 的兼容性

自从 2015-0-08 起被 ​​W3C 推荐​​使用以来,经过多年的发展,伴随着 ​​IE 浏览器退出历史舞台​​,现代浏览器对 ​​IndexedDB 支持情况​​甚是友好。

Web 中使用 IndexedDB 实现缓存_JavaScript_05

参考文章

  • ​​浏览器数据库 IndexedDB 入门教程​​
  • ​​Web Storage API​​
  • ​​IndexedDB API​​
  • ​​HTML5 indexedDB前端本地存储数据库实例教程​​
  • ​​IndexedDB使用(基本函数封到Angular2的service里)​​
  • ​​Indexed Database API 3.0​​
  • ​​Browser storage limits and eviction criteria​​


举报

相关推荐

0 条评论