前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Web 中使用 IndexedDB 实现缓存

Web 中使用 IndexedDB 实现缓存

作者头像
Jimmy_is_jimmy
发布2022-11-21 15:31:55
1.3K0
发布2022-11-21 15:31:55
举报
文章被收录于专栏:call_me_R

theme: fancy

说起 Web 缓存,我们自然就会想到 CookieLocalStorageSessionStorage,却很少提及 IndexedDB

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

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

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

IndexedDB 是什么

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

在此之前还有一个类似数据库 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 代码片段 进行了解。

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

IndexedDB 实现案例

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

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

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

案例完成的功能有:

  • 连接 IndexedDB 并创建对象(表)及索引
  • 获取记录列表的信息。也就是图中 table 的数据
  • 增加列表的数据,更新 IndexedDB 中的数据
  • 编辑列表的数据,更新 IndexedDB 中的数据
  • 删除列表的数据,更新 IndexedDB 中的数据
  • 选中列表中的一条数据,从 IndexedDB 中读取并展示在 当前选中 位置

案例采用的 UI 框架是 Ant Design of Angular

代码即文档,详细代码和解析如下:

代码语言:javascript
复制
<!--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>
代码语言:javascript
复制
// 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 的存储数据不变。

IndexedDB 的兼容性

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

参考文章

推荐阅读

本文正在参加「金石计划 . 瓜分6万现金大奖」

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • theme: fancy
    • IndexedDB 是什么
      • IndexedDB 能够解决什么
        • IndexedDB 实现案例
          • IndexedDB 的兼容性
            • 参考文章
              • 推荐阅读
              相关产品与服务
              文件存储
              文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档