indexeddb:part03-objectstore

第三部分:对象存储空间(Object Store)详解

对象存储空间(Object Store)是 IndexedDB 中最核心的概念,它是实际存储数据的地方。理解对象存储空间的设计和使用方式,是掌握 IndexedDB 的关键。

对象存储空间可以类比为关系型数据库中的“表”,但有几个重要区别:

  • 无模式(Schema-less):同一对象存储空间中的对象不需要有相同的结构
  • 对象导向:存储的是 JavaScript 对象,而非行记录
  • 主键必需:每个对象必须有一个唯一的主键
  • 索引可选:可以创建索引加速查询,但不是必须的
特性 说明
存储类型 结构化克隆算法支持的任何 JavaScript 对象
主键要求 每个对象必须有唯一主键
数据类型 对象、数组、日期、Blob、ArrayBuffer 等
容量限制 受浏览器存储配额限制
并发访问 通过事务控制,同一时间只有一个写入事务
// 基本语法
const store = db.createObjectStore(storeName, options);
 
// options 参数
{
  keyPath: string | string[],    // 指定主键属性路径
  autoIncrement: boolean         // 是否自动生成主键
}

策略一:使用内联键(keyPath)

// 单一属性作为主键
db.createObjectStore('users', { keyPath: 'userId' });
 
// 使用对象时
store.add({
  userId: 'U001',
  name: '张三',
  email: 'zhangsan@example.com'
});

优点:

  • 主键与数据紧密关联
  • 查询直观,知道主键就知道数据位置

缺点:

  • 需要应用层生成唯一 ID
  • 不能更改主键值

策略二:使用自增键(autoIncrement)

// 自动生成递增整数
db.createObjectStore('logs', { autoIncrement: true });
 
// 插入数据时不需要指定主键
store.add({
  message: '用户登录',
  timestamp: new Date(),
  level: 'info'
});
// 自动分配主键 1, 2, 3...

优点:

  • 无需应用层管理主键
  • 保证唯一性和顺序性

缺点:

  • 主键是数字,不携带业务信息
  • 删除数据后主键不会复用

策略三:组合使用 keyPath 和 autoIncrement

// 自动生成主键并存储在对象的 id 属性中
db.createObjectStore('items', { 
  keyPath: 'id',
  autoIncrement: true 
});
 
// 插入后对象会自动包含 id 属性
const request = store.add({ name: '商品A', price: 100 });
request.onsuccess = () => {
  console.log('分配的主键:', request.result); // 1
};

策略四:复合主键

// 使用多个属性组成复合主键
db.createObjectStore('memberships', {
  keyPath: ['userId', 'projectId']
});
 
// 插入数据
store.add({
  userId: 'U001',
  projectId: 'P100',
  role: 'admin',
  joinedAt: new Date()
});
 
// 查询时使用数组作为键
store.get(['U001', 'P100']);
request.onupgradeneeded = function(event) {
  const db = event.target.result;
 
  const store = db.createObjectStore('products', { 
    keyPath: 'sku' 
  });
 
  // 普通索引
  store.createIndex('categoryIndex', 'category', { unique: false });
 
  // 唯一索引
  store.createIndex('barcodeIndex', 'barcode', { unique: true });
 
  // 多入口索引(用于数组属性)
  store.createIndex('tagIndex', 'tags', { multiEntry: true });
 
  // 复合索引
  store.createIndex('priceCategoryIndex', ['price', 'category']);
};
const store = transaction.objectStore('users');
 
// 获取对象存储空间名称
console.log(store.name); // 'users'
 
// 获取主键路径
console.log(store.keyPath); // 'id' 或 null
 
// 检查是否使用自增主键
console.log(store.autoIncrement); // true 或 false
 
// 获取索引列表
console.log(store.indexNames); // DOMStringList ['nameIndex', 'emailIndex']
const request = store.add(value, key);
 
// 示例
const addRequest = store.add({
  id: 'user001',
  name: '张三',
  email: 'zhangsan@example.com',
  profile: {
    age: 25,
    city: '北京'
  }
});
 
addRequest.onsuccess = function() {
  console.log('添加成功,主键:', addRequest.result);
};
 
addRequest.onerror = function() {
  // 如果主键已存在,会触发错误
  console.error('添加失败:', addRequest.error);
};

注意事项:

  • 如果主键已存在,add() 会失败(触发 ConstraintError)
  • 如果不指定 key 参数,使用 keyPath 或 autoIncrement
  • 数据通过结构化克隆算法存储,函数和某些 DOM 对象不能被存储
// put 会覆盖已存在的记录,或插入新记录
const request = store.put(value, key);
 
// 更新现有记录
store.put({
  id: 'user001',  // 相同主键
  name: '张三(已更新)',
  email: 'newemail@example.com'
});
 
// 插入新记录(如果不存在)
store.put({
  id: 'user002',
  name: '李四',
  email: 'lisi@example.com'
});

add() vs put() 对比:

方法 主键已存在 主键不存在
add() 错误 (ConstraintError) 插入新记录
put() 覆盖现有记录 插入新记录
// 删除指定主键的记录
const deleteRequest = store.delete(key);
 
// 示例
store.delete('user001');
 
// 也可以使用 IDBKeyRange 删除范围
store.delete(IDBKeyRange.bound('A', 'M'));
// 删除所有记录
const clearRequest = store.clear();
 
clearRequest.onsuccess = function() {
  console.log('对象存储空间已清空');
};
// 通过主键获取
const getRequest = store.get(key);
 
getRequest.onsuccess = function() {
  if (getRequest.result) {
    console.log('找到数据:', getRequest.result);
  } else {
    console.log('数据不存在');
  }
};
 
// 获取所有记录(配合游标,见第七部分)
// 获取记录数量
const countRequest = store.count();
countRequest.onsuccess = () => {
  console.log('记录数:', countRequest.result);
};

IndexedDB 使用结构化克隆算法序列化数据,支持以下类型:

  • 基本类型:Boolean, Number, String, null, undefined
  • 日期:Date 对象
  • 正则表达式:RegExp(但不支持标志)
  • 二进制数据:Blob, File, FileList, ArrayBuffer, TypedArray, DataView
  • 图像数据:ImageData
  • 集合:Array, Object, Map, Set
  • 特殊对象:Error(部分属性可能丢失)

以下类型不能直接存储:

  • Function:函数对象
  • DOM 对象:大多数 DOM 节点
  • 某些内置对象:如 WeakMap, WeakSet
  • 循环引用:对象间存在循环引用时会抛出 DataCloneError
// 方案一:将函数转换为字符串
const obj = {
  name: 'test',
  // 存储前转换
  fn: myFunction.toString()
};
 
// 方案二:使用 JSON 序列化(丢失类型信息)
const serialized = JSON.stringify(obj);
// 存储字符串
store.add({ id: 1, data: serialized });
 
// 方案三:自定义序列化
function serialize(obj) {
  return JSON.stringify(obj, (key, value) => {
    if (value instanceof Date) {
      return { __type: 'Date', value: value.toISOString() };
    }
    return value;
  });
}
 
function deserialize(str) {
  return JSON.parse(str, (key, value) => {
    if (value && value.__type === 'Date') {
      return new Date(value.value);
    }
    return value;
  });
}

每个对象存储空间只存储一种类型的数据:

// 用户存储
const userStore = db.createObjectStore('users', { keyPath: 'id' });
 
// 订单存储
const orderStore = db.createObjectStore('orders', { keyPath: 'orderId' });
 
// 产品存储
const productStore = db.createObjectStore('products', { keyPath: 'sku' });

优点:

  • 结构清晰,易于维护
  • 索引针对特定类型优化
  • 事务粒度合理

在同一个对象存储空间中存储不同类型,通过 type 字段区分:

const store = db.createObjectStore('items', { keyPath: 'id' });
store.createIndex('typeIndex', 'type', { unique: false });
 
// 存储用户
store.add({ id: 'u1', type: 'user', name: '张三' });
 
// 存储产品
store.add({ id: 'p1', type: 'product', title: 'iPhone' });
 
// 存储文章
store.add({ id: 'a1', type: 'article', title: 'IndexedDB 教程' });

优点:

  • 可以统一查询所有类型
  • 适合日志等类型相似的数据

缺点:

  • 索引设计复杂
  • 类型检查需要在应用层处理

模拟关系型数据库的外键关系:

// 用户表
const userStore = db.createObjectStore('users', { keyPath: 'userId' });
 
// 订单表,包含 userId 作为外键
const orderStore = db.createObjectStore('orders', { keyPath: 'orderId' });
orderStore.createIndex('userIdIndex', 'userId', { unique: false });
 
// 订单项表
const itemStore = db.createObjectStore('orderItems', { keyPath: 'itemId' });
itemStore.createIndex('orderIdIndex', 'orderId', { unique: false });

本章深入介绍了对象存储空间:

  • 对象存储空间是 IndexedDB 的核心数据容器
  • 主键设计有内联键、自增键、复合键等多种策略
  • 基本操作包括 add、put、delete、clear、get
  • 使用结构化克隆算法存储数据,注意不支持函数等类型
  • 设计模式包括单类型、多态和关系存储

继续阅读 第四部分:索引(Index)详解

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • indexeddb/part03-objectstore.txt
  • 最后更改: 2026/04/27 19:51
  • 张叶安