Forget about localStorage, indexedDB is the new favorite for front-end storage!

原创Feng Xicai/Jiaojiao Technical Team

foreword

During the project development process, the front end needs to store a large amount of data. cookie, localstorage have storage length limit.
List of forms

characteristic cookie localStorage sessionStorage indexedDB
data life cycle Generally generated by the server, you can set the expiration time; components such as front-end and js-cookie can also be generated It will always exist unless it is cleaned; it will be saved locally when the browser is closed, but it does not support cross-browser Cleaning and refreshing still exist when the page is closed, and cross-page interaction is not supported Persists until cleared
data storage size 4K 5M 5M unlimited size
communicate with the server It will be carried in the header of the request every time, which will affect the performance of the request; at the same time, since it is carried in the request, it is also prone to security problems Does not participate Does not participate Does not participate
features String key-value pairs store data locally String key-value pairs store data locally String key-value pairs store data locally IndexedDB is a non-relational database (does not support operations via SQL statements). It can store a large amount of data, provide an interface to query, and can also build indexes. These are capabilities that other storage solutions cannot provide.

We need a front-end storage solution with large storage capacity, support for search and custom indexing, so we chose it.
Check the indexedDB support on caniuse, and the current browser support is good.
caniuse.com/?search=ind…
image.png

Introduction to IndexedDB

IndexedDB is a non-relational database. (SQL queries are not supported)

Features:

  • Key-value pair storage IndexedDB internally uses an object store (object store) to store data. All types of data can be stored directly, including JavaScript objects. In the object warehouse, data is stored in the form of "key-value pairs". Each data record has a corresponding primary key. The primary key is unique and cannot be duplicated, otherwise an error will be thrown.
  • 异步 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
  • 支持事务 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
  • 同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
  • 支持二进制储存 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象。
  • 储存空间大 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。储 存 在 电 脑 上 中 的 位 置 为 C:\Users\当 前 的 登 录 用 户\AppData\Local\Google\Chrome\User Data\Default\IndexedDB

核心概念

  • 数据库:IDBDatabase 对象,数据库有版本概念,同一时刻只能有一个版本,每个域名可以建多个数据库
  • 对象仓库:IDBObjectStore 对象,类似于关系型数据库的表格
  • 索引: IDBIndex 对象,可以在对象仓库中,为不同的属性建立索引,主键建立默认索引
  • 事务: IDBTransaction 对象,增删改查都需要通过事务来完成,事务对象提供了error,abord,complete三个回调方法,监听操作结果
  • 操作请求:IDBRequest 对象
  • 指针: IDBCursor 对象
  • 主键集合:IDBKeyRange 对象,主键是默认建立索引的属性,可以取当前层级的某个属性,也可以指定下一层对象的属性,还可以是一个递增的整数

indexedDB使用

基础操作

1. 创建数据库 & 新建表和索引

/*
 *@databaseName 数据仓库的名字
 *@version 数据仓库的版本
 *@databaseName 数据仓库的名字
 */

var request = window.indexedDB.open('group', 1);

/*
 *数据仓库打开失败
 */
request.onerror = function(error) {
  console.log('IndexedDB 打开失败', error);
};

/*
 *数据仓库打开成功
 */
request.onsuccess = function(res) {
  console.log('IndexedDB 打开成功', res);
  db = res.target.result;
};

/*
 *数据仓库升级事件(第一次新建库是也会触发,因为数据仓库从无到有算是升级了一次)
 */
request.onupgradeneeded = function(res) {
  console.log('IndexedDB 升级成功', res);
  db = res.target.result;
  db_table = db.createObjectStore('group', { keyPath: 'id' });
  db_table.createIndex('indexName', 'name', { unique: false });
};

image.png

image.png

2. 新增数据

/*
 *新建事务
 *@params 数据仓库的数组
 *@params 写入模式
 */
var store = db.transaction(['group'], 'readwrite').objectStore('group');

/*
 *add方法添加数据
 *@params 需要添加的数据信息
 */
var request = store.add({
  id: new Date().getTime(),
  name: '王二',
  age: 12,
  email: '[email protected]',
});

/*
 *添加成功
 */
request.onsuccess = function(event) {
  console.log('数据添加成功', event);
};

/*
 *添加失败
 */
request.onerror = function(event) {
  console.log('数据添加失败', event);
};

image.png

image.png

3. 读取数据


/*
 *新建事务
 *@params 数据仓库的数组
 */
var store = db.transaction(['group']).objectStore('group');

/*
 *get方法获取数据
 *@params 数据的主键
 */
var request = store.get(1678664831491);

/*
 *获取成功
 */
request.onsuccess = function(event) {
  if (event.target.result) {
    console.log('数据获取成功', event.target.result);
  } else {
    console.log('未获取到数据');
  }
};

/*
 *获取失败
 */
request.onerror = function(event) {
  console.log('数据获取失败', event);
};

image.png

4. 更新数据

/*
 *新建事务
 *@params 数据仓库的数组
 *@params 写入模式
 */
var store = db.transaction(['group'], 'readwrite').objectStore('group');

/*
 *put方法根据主键更新数据
 *@params 数据的主键
 */
var request = store.put({
  id: 1678664831491,
  name: '张一' + Math.random(),
  age: 24,
  email: '[email protected]',
});

/*
 *更新成功
 */
request.onsuccess = function(event) {
  console.log('数据更新成功', event);
};

/*
 *更新失败
 */
request.onerror = function(event) {
  console.log('数据更新失败', event);
};


未加 readwrite, 会抛错,修改数据失败

image.png
image.png

image.png

5. 删除数据

/*
*新建事务
*@params 数据仓库的数组
*/
var store = db.transaction(['group'], 'readwrite').objectStore('group');

/*
*delete方法删除数据
*@params 数据的主键
*/
var request = store.delete(1678664831491); 

/*
*删除成功
*/
request.onsuccess = function (event) {
    console.log('数据删除成功',event);
};

/*
*删除失败
*/
request.onerror = function (event) {
    console.log('数据删除失败',event);
};

image.png
image.png

6. 使用索引

/*
*新建事务
*@params 数据仓库的数组
*/
var store = db.transaction(['group']).objectStore('group');

/*
*index方法获取索引对象
*get方法获取数据
*@params 数据的索引
*/
var request = store.index('indexName').get('张四'); 

/*
*获取成功
*/
request.onsuccess = function (event) {
     console.log('通过索引获取数据成功',event.target.result);
};

/*
*获取失败
*/
request.onerror = function (event) {
    console.log('通过索引获取数据失败',event);
};

image.png

image.png

7. 获取整张表所有的data


var store = db.transaction(['group']).objectStore('group');
var request = store.getAll();

/*
 *更新成功
 */
request.onsuccess = function(event) {
  console.log('indexedDB getAll:', event.target.result);
};

/*
 *更新失败
 */
request.onerror = function(event) {
  console.log('indexedDB getAll:', event);
};

image.png

8. 根据指定条件获取data

首先让我们 来了解 IDBKeyRange 的API
www.w3cschool.cn/javascript_…
image.png


var store = db.transaction(['group']).objectStore('group');
// 获取id名称小于当前时间的所有data
var request = store.getAll(IDBKeyRange.upperBound(+new Date()));

/*
 *更新成功
 */
request.onsuccess = function(event) {
  console.log('indexedDB getAll:', event.target.result);
};

/*
 *更新失败
 */
request.onerror = function(event) {
  console.log('indexedDB getAll:', event);
};

image.png

业务中优雅使用

indexedDB 并非无底洞,可以无限存储。要考虑做定期删除等功能

1. 定期删除失效数据

1. 首先我们创建数据的时候就以时间戳+失效时间来约定id规则

image.png

2. 再通过上面基础操作的getAll方法,获取指定条件的data,再遍历data,调用删除数据API

var store = db.transaction(['group'], 'readwrite').objectStore('group');
var request = store.getAll(IDBKeyRange.upperBound(+new Date()));

/*
 *更新成功
 */
request.onsuccess = function(event) {
  console.log('indexedDB getAll:', event);
  console.log('indexedDB getAll:', event.target.result);
  const data = event.target.result;
  data.forEach(item => {
    console.log('删除数据', item);
    const deletRequest = store.delete(item.id);
    /*
     *删除成功
     */
    deletRequest.onsuccess = function(event) {
      console.log('数据删除成功', event);
    };


    /*
     *删除失败
     */
    deletRequest.onerror = function(event) {
      console.log('数据删除失败', event);
    };
  });
};


/*
 *更新失败
 */
request.onerror = function(event) {};

image.png

3. 我们把上述方法包装下每次打开页面,清空下失效数据。就可以实现一个定期删除失效数据的方法啦

2. 批量添加数据


const TestData = [
    {
      event: 'NE-TEST1',
      level: 'warning',
      errorCode: 200,
      url: 'http://www.example.com',
      time: '2017/11/8 下午4:53:039',
      isUploaded: false
    },
    {
      event: 'NE-TEST2',
      msg: '测试2',
      level: 'error',
      errorCode: 1000,
      url: 'http://www.example.com',
      time: '2017/11/8 下午4:53:042',
      isUploaded: false
    },
    {
      event: 'NE-TEST3',
      msg: '测试3',
      level: 'info',
      errorCode: 3000,
      url: 'http://www.example.com',
      time: '2017/11/8 下午4:53:043',
      isUploaded: false
    },
    {
      event: 'NE-TEST4',
      mgs: '测试4',
      level: 'info',
      url: 'http://www.example.com',
      time: '2017/11/8 下午4:53:0423',
      isUploaded: false
    }
  ]

/**
* 添加数据
* @param {array} docs 要添加数据
* @param {string} objName 仓库名称
*/
function addData (docs, objName) {
    if (!(docs && docs.length)) {
      throw new Error('docs must be a array!')
    }
    return openIndexedDB().then(db => {
      const tx = db.transaction([objName], 'readwrite')
      tx.oncomplete = e => {
        console.log('tx:addData onsuccess', e)
        return Promise.resolve(docs)
      }
      tx.onerror = e => {
        e.stopPropagation()
        console.error('tx:addData onerror', e.target.error)
        return Promise.reject(e.target.error)
      }
      tx.onabort = e => {
        console.warn('tx:addData abort', e.target)
        return Promise.reject(e.target.error)
      }
      const obj = tx.objectStore(objName)
      docs.forEach(doc => {
        const req = obj.add(doc)
        /**
         * NOTE:
         * request
         * 两个事件:
         * 1. success
         * 2. error
         */
        // req.onsuccess = e => console.log('obj:addData onsuccess', e.target)
        req.onerror = e => {
          console.error('obj:addData onerror', e.target.error)
        }
      })
    })
  }

  addData(TestData, OB_NAMES.UseKeyGenerator)
.then(() => addData(TestData, OB_NAMES.UseKeyPath))

结尾

一些封装好的库

localforage 推荐 ⭐️⭐️⭐️⭐️⭐️ (我们当前业务就用的这个~)

localsotrage使用保持一致,更适合前端使用
还在用localStorage?快来试试localForage吧! - 掘金

IndexedDBWrapper 推荐 ⭐️⭐️⭐️

思考我们还可以用indexedDB做什么

1. 用户使用日志收集

在做一些前端electron应用,webApp,我们可以定义一个log日志库,来收集用户日志,遇到问题时,可以让用户,打包上传到日志库,排查跟进解决用户反馈问题。

定义日志上报结构
// 定义log基本结构
const LogItem = {
	level: 'log' | 'info' | 'error' ...,
  tag: 'request' | 'system' | ‘video’ | 'audio' | 'domClick' ... ,
	msg: ...,  // any
  date: +new Date(),
	...
}
导出所有数据,并上传

此处就可以用上面的 getAll 方法,获取该表所有数据,生成json打包上传到自己公司的日志库。

2. request层封装,对不长更新接口缓存

封装request方法,缓存请求接口,物理缓存数据~让页面接口数据加载飞起来

3. 大文件上传,分片,避免网络失败,刷新页面等导致中断问题

After the file is sliced, it is stored in the library first indexedDB, and the upload status is dynamically updated. The abnormal situation can be taken out and then continue to locate the unuploaded slice and continue uploading.

postscript

reference documents

Guess you like

Origin juejin.im/post/7239259798267904059