IndexedDB 基础操作入门

发布时间 2023-04-18 15:06:30作者: 樊顺

IndexedDB 打开数据库、建立连接、建表、加索引字段、添加、更新、删除、查询等操作介绍。

示例代码:

/**
 * 1. 键值对存储
 * 内部采用对象仓库(object store)存放数据,支持所有类型数据的存入。
 * 在数据仓库中,数据以"键值对"的形式保存,数据记录都有唯一的主键(不可重复);
 * 
 * 2. 异步
 * 
 * 3. 支持事务
 * 在一系列操作步骤中,只要有一步失败,整个事务就会取消,数据库回滚到事务发生之前。
 * 
 * 4. 同源限制
 * 每一个数据库对应创建它的域名(只能访问自身域名下的数据库)
 * 
 * 5. 存储空间大
 * 一般不少于250MB
 * 
 * 6. 支持二进制存储
 * 支持二进制数据(ArrayBuffer, Blob等)
 * 
 * 数据库:  IDBDatabase 对象
 * 对象仓库: IDBObjectStore 对象
 * 索引:    IDBIndex 对象
 * 事务:    IDBTransaction 对象
 * 操作请求: IDBRequest 对象
 * 指针:    IDBCursor 对象
 * 主键集合: IDBKeyRange 对象
 * 
 * indexedDB数据库具有版本的概念(同一时刻,只有一个版本的数据库存在)
 * 每个数据库包含若干个对象仓库(object store,类似于关系型数据库中的表格)
 */

// 数据库名称
const databaseName = 'todo-list';
// 默认数据库版本
const defaultDatabaseVersion = 1;

/**
 * DB instance
 * @type {IDBDatabase}
 */
let db;

// 测试数据
const data = [
  { todo: 'minutes', name: 'a', id: 10 },
  { todo: 'day', name: 'b', id: 3 },
  { todo: 'month', name: 'c', id: 9 },
  { todo: 'year', name: 'd', id: 2019 },
  { todo: 'notified', name: 'e', id: 0 },
];

// 表名
const tableName = 'todos';

/**
 * 打开数据库请求
 * open(name: string, version?: number | undefined): IDBOpenDBRequest;
 * IDBOpenDBRequest对象所具备的事件:
 * 1. onerror
 * 2. onsuccess
 * 3. onupgradeneeded(数据库升级事件)
 * 4. onblocked
 */
const DBOpenRequest = window.indexedDB.open(databaseName, defaultDatabaseVersion);

// 数据库连接请求失败事件
DBOpenRequest.addEventListener('error', ev => {
  console.log('DBOpenRequest Error: ', ev);
});

// 数据库连接请求成功事件
DBOpenRequest.addEventListener('success', () => {
  console.info('连接成功!');
  db = DBOpenRequest.result;
});

// 在当一个数据库的版本比已经存在的版本还高的时候触发
DBOpenRequest.addEventListener('upgradeneeded', ev => {
  db = ev.target.result;
  console.log(`版本更新至 ${db.version}`);
  
  db.addEventListener('error', ev => {
    console.log('数据库加载时出错', ev);
  });

  // 主键
  const keyPath = 'id';
  // 新建表
  let store = null;
  // 如果目标表不存在
  if (!db.objectStoreNames.contains(tableName)) {
    store = db.createObjectStore(tableName, {
      // 表的主键
      keyPath,
    });
  }
  // 新建索引字段
  store.createIndex('name', 'name', { unique: false });
  console.info('当前表:', store);
});

/**
 * *初始化现有的数据项*
 */
function initItems(list) {
  for (const item of list) {
    addItem(item);
  }
}
initItems(data);

/**
 * 添加数据项
 */
function addItem(item) {
  const store = db.transaction(tableName, 'readwrite').objectStore(tableName);
  const request = store.add(item);
  request.addEventListener('success', () => {
    console.log('数据写入成功!');
  });
  request.addEventListener('error', () => {
    console.log('数据写入失败!');
  });
}
// addItem({ id: 1, name: 'k', todo: 'link' });

/**
 * 更新单个数据项
 */
function updateItem(item) {
  const store = db.transaction(tableName, 'readwrite').objectStore(tableName);
  const request = store.put(item);
  request.addEventListener('success', () => {
    console.log('数据更新成功!');
  });
  request.addEventListener('error', () => {
    console.log('数据更新失败!');
  });
}
// updateItem({ id: 1, name: 'j', todo: 'link' });

/**
 * 读取指定范围内的数据项列表
 * @param {[(string|number), (string|number)]} range
 * @param {string} idxName 
 */
function readByRange(range, idxName) {
  const transaction = db.transaction(tableName, 'readonly');
  const store = transaction.objectStore(tableName).index(idxName);
  const [start, end] = range;
  // 查询范围
  const keyRange = window.IDBKeyRange.bound(start, end);
  // 查询游标对象
  const cursor = store.openCursor(keyRange);

  cursor.addEventListener('success', ev => {
    const res = ev.target.result;
    if (!res) {
      console.log('没有更多数据了!');
      return;
    }
    console.info('条目:', res.value);
    res.continue();
  });
  cursor.addEventListener('error', ev => {
    console.log('范围查询失败:', ev);
  });
}

/**
 * 读取单个数据项(根据当前索引键的特定值)
 */
function readItem(id) {
  const transaction = db.transaction([tableName]);
  const store = transaction.objectStore(tableName);
  const request = store.get(id);

  request.addEventListener('success', () => {
    if (!request.result) {
      console.log('未获得数据记录');
      return;
    }
    console.info('id: ' + request.result.id);
    console.info('name: ' + request.result.name);
    console.info('todo: ' + request.result.todo);
  });
  request.addEventListener('error', () => {
    console.log('事务执行失败!');
  });
}

/**
 * 删除匹配到的数据项(id值)
 */
function deleteItem(id) {
  const store = db.transaction(tableName, 'readwrite').objectStore(tableName);
  const request = store.delete(id);
  request.addEventListener('success', () => {
    console.log('数据删除成功!');
  });
  request.addEventListener('error', () => {
    console.log('数据删除失败!');
  });
}

/**
 * 查询所有数据项
 */
function queryAll() {
  const store = db.transaction(tableName, 'readonly').objectStore(tableName);
  const cursor = store.openCursor();
  cursor.addEventListener('success', evt => {
    const res = evt.target.result;
    if (!res) {
      console.log('没有更多数据了!');
      return;
    }
    console.info('id: ' + res.value.id);
    console.info('name: ' + res.value.name);
    console.info('todo: ' + res.value.todo);
    res.continue();
  });
  cursor.addEventListener('error', () => {
    console.log('查询结果集失败!');
  });
}

 

参考:浏览器数据库 IndexedDB 入门教程 - 阮一峰