目录

第九部分:高级特性

9.1 二进制数据存储

9.1.1 Blob 和 File

IndexedDB 可以直接存储 Blob 和 File 对象:

// 存储图片
async function storeImage(file) {
  const transaction = db.transaction(['images'], 'readwrite');
  const store = transaction.objectStore('images');
 
  const imageRecord = {
    id: generateId(),
    name: file.name,
    type: file.type,
    size: file.size,
    data: file, // 直接存储 File 对象
    uploadedAt: new Date().toISOString()
  };
 
  return new Promise((resolve, reject) => {
    const request = store.add(imageRecord);
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}
 
// 读取图片并显示
async function loadImage(id) {
  const transaction = db.transaction(['images'], 'readonly');
  const store = transaction.objectStore('images');
 
  const record = await new Promise((resolve, reject) => {
    const request = store.get(id);
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
 
  if (record) {
    const url = URL.createObjectURL(record.data);
    const img = document.createElement('img');
    img.src = url;
    document.body.appendChild(img);
  }
}

9.1.2 ArrayBuffer 和 TypedArray

// 存储二进制数据
async function storeBinaryData(id, arrayBuffer) {
  const transaction = db.transaction(['binaryData'], 'readwrite');
  const store = transaction.objectStore('binaryData');
 
  const record = {
    id,
    data: arrayBuffer,
    byteLength: arrayBuffer.byteLength,
    storedAt: new Date().toISOString()
  };
 
  await store.put(record);
}
 
// 存储 TypedArray
async functionStoreTypedArray(id, floatArray) {
  const transaction = db.transaction(['numbers'], 'readwrite');
  const store = transaction.objectStore('numbers');
 
  await store.put({
    id,
    data: floatArray, // Float32Array 等
    type: floatArray.constructor.name
  });
}

9.2 在 Web Worker 中使用

9.2.1 Worker 中的数据库操作

// worker.js
let db = null;
 
self.onmessage = function(event) {
  const { action, data } = event.data;
 
  switch (action) {
    case 'open':
      openDatabase(data.dbName, data.version);
      break;
    case 'add':
      addData(data.storeName, data.record);
      break;
    case 'get':
      getData(data.storeName, data.key);
      break;
    case 'query':
      queryData(data.storeName, data.index, data.range);
      break;
  }
};
 
function openDatabase(dbName, version) {
  const request = indexedDB.open(dbName, version);
 
  request.onupgradeneeded = function(event) {
    const db = event.target.result;
    if (!db.objectStoreNames.contains('data')) {
      db.createObjectStore('data', { keyPath: 'id' });
    }
  };
 
  request.onsuccess = function(event) {
    db = event.target.result;
    self.postMessage({ type: 'ready' });
  };
 
  request.onerror = function(event) {
    self.postMessage({ type: 'error', error: event.target.error.message });
  };
}
 
function addData(storeName, record) {
  const transaction = db.transaction([storeName], 'readwrite');
  const store = transaction.objectStore(storeName);
 
  const request = store.add(record);
  request.onsuccess = () => {
    self.postMessage({ type: 'added', key: request.result });
  };
}
 
function getData(storeName, key) {
  const transaction = db.transaction([storeName], 'readonly');
  const store = transaction.objectStore(storeName);
 
  const request = store.get(key);
  request.onsuccess = () => {
    self.postMessage({ type: 'result', data: request.result });
  };
}
 
// main.js
const worker = new Worker('worker.js');
 
worker.postMessage({
  action: 'open',
  data: { dbName: 'WorkerDB', version: 1 }
});
 
worker.onmessage = function(event) {
  if (event.data.type === 'ready') {
    console.log('Worker 数据库就绪');
 
    // 发送数据
    worker.postMessage({
      action: 'add',
      data: {
        storeName: 'data',
        record: { id: 1, value: '测试数据' }
      }
    });
  }
 
  if (event.data.type === 'added') {
    console.log('数据已添加:', event.data.key);
  }
};

9.3 大数据集处理

9.3.1 流式处理

// 处理大量数据,避免内存溢出
async function* streamRecords(storeName, batchSize = 100) {
  const transaction = db.transaction([storeName], 'readonly');
  const store = transaction.objectStore(storeName);
 
  let batch = [];
 
  await new Promise((resolve, reject) => {
    const request = store.openCursor();
 
    request.onsuccess = (event) => {
      const cursor = event.target.result;
 
      if (cursor) {
        batch.push(cursor.value);
 
        if (batch.length >= batchSize) {
          resolve();
        } else {
          cursor.continue();
        }
      } else {
        resolve();
      }
    };
 
    request.onerror = () => reject(request.error);
  });
 
  if (batch.length > 0) {
    yield batch;
  }
}
 
// 使用
for await (const batch of streamRecords('logs', 500)) {
  console.log('处理批次:', batch.length);
  await processBatch(batch);
}

9.3.2 数据导入导出

// 导出为 JSON
async function exportToJSON(storeName) {
  const transaction = db.transaction([storeName], 'readonly');
  const store = transaction.objectStore(storeName);
 
  const records = await store.getAll();
 
  // 处理不可序列化的数据
  const serialized = records.map(record => {
    const copy = { ...record };
    // 将 Blob 转为 base64 或标记
    if (copy.data instanceof Blob) {
      copy._hasBlob = true;
      copy._blobType = copy.data.type;
      delete copy.data; // 不导出二进制数据
    }
    return copy;
  });
 
  return JSON.stringify(serialized, null, 2);
}
 
// 从 JSON 导入
async function importFromJSON(storeName, jsonString) {
  const records = JSON.parse(jsonString);
 
  const transaction = db.transaction([storeName], 'readwrite');
  const store = transaction.objectStore(storeName);
 
  for (const record of records) {
    await store.put(record);
  }
}

9.4 复合索引高级用法

9.4.1 多字段查询

// 创建复合索引
const store = db.createObjectStore('products', { keyPath: 'sku' });
store.createIndex('categoryPriceIndex', ['category', 'price']);
store.createIndex('brandRatingIndex', ['brand', 'rating'], { unique: false });
 
// 查询特定分类中价格范围内的产品
function findProducts(category, minPrice, maxPrice) {
  const transaction = db.transaction(['products'], 'readonly');
  const store = transaction.objectStore('products');
  const index = store.index('categoryPriceIndex');
 
  // 复合索引的范围查询
  const range = IDBKeyRange.bound(
    [category, minPrice],
    [category, maxPrice]
  );
 
  return index.getAll(range);
}
 
// 查询特定品牌的所有产品(忽略第二个字段)
function findByBrand(brand) {
  const transaction = db.transaction(['products'], 'readonly');
  const store = transaction.objectStore('products');
  const index = store.index('brandRatingIndex');
 
  // 只指定第一个字段,第二个字段用范围
  const range = IDBKeyRange.bound(
    [brand, -Infinity],
    [brand, Infinity]
  );
 
  return index.getAll(range);
}

9.5 存储持久化

9.5.1 请求持久存储

async function requestPersistentStorage() {
  if (navigator.storage && navigator.storage.persist) {
    const isPersistent = await navigator.storage.persist();
 
    if (isPersistent) {
      console.log('已获取持久存储权限');
    } else {
      console.log('持久存储请求被拒绝');
    }
 
    return isPersistent;
  }
  return false;
}
 
// 检查存储持久化状态
async function checkPersistence() {
  if (navigator.storage && navigator.storage.persisted) {
    const persisted = await navigator.storage.persisted();
    console.log('存储是否持久化:', persisted);
    return persisted;
  }
  return false;
}

9.6 本章小结

本章介绍了 IndexedDB 的高级特性:

继续阅读 第十部分:性能优化