使用 IndexedDB

索引資料庫基本概念指南。

本指南說明 IndexedDB API 的基本概念。我們使用 Jake Archibald 的 IndexedDB Promised 程式庫,這個程式庫與 IndexedDB API 非常類似,但使用了 promise (語法更簡潔的語法,您可以 await)。這可以簡化 API,同時維持 API 的結構。

什麼是 IndexedDB?

IndexedDB 是大規模的 NoSQL 儲存系統,可存放使用者瀏覽器中的任何內容。除了一般的搜尋、取得和放置動作以外,IndexedDB 也支援交易。MDN 上 IndexedDB 的定義:

IndexedDB 是一種低階 API,適合用於用戶端儲存大量結構化資料 (包括檔案/blob)。這個 API 會使用索引,以便針對這項資料執行高效能搜尋。雖然 DOM 儲存空間適合用來儲存少量資料,但較不適合儲存大量結構化資料。IndexedDB 提供解決方案。

每個 IndexedDB 資料庫都具有專屬的「來源」 (通常是網站網域或子網域),也就是說,其他來源無法存取或存取該資料庫。資料儲存空間上限通常都十分龐大,如果確實有這類限制,但各個瀏覽器處理限制和資料清除的方式不同。詳情請參閱延伸閱讀一節。

IndexedDB 字詞

資料庫
IndexedDB 的最高等級。其中包含物件儲存庫,以及您要保存的資料。您可以使用任何名稱建立多個資料庫。
物件存放區
用來儲存資料的個別值區。您可以將物件儲存庫視為傳統關聯資料庫中的資料表。一般而言,儲存的資料的每種「類型」 (非 JavaScript 資料類型) 都有一個物件儲存區。舉例來說,假設某個應用程式持續顯示網誌文章和使用者個人資料,那麼你可以將兩個物件儲存起來。與傳統資料庫中的資料表不同,商店內實際的 JavaScript 資料類型不必一致 (例如,如果 people 物件儲存庫中有三名使用者,其年齡屬性可能是 53'twenty-five'unknown)。
索引
一種物件儲存庫,可透過資料的個別屬性整理其他物件儲存庫 (稱為參照物件儲存庫) 中的資料。索引可以由這個屬性擷取物件儲存庫中的記錄。舉例來說,如要儲存使用者,建議日後再輸入使用者名稱、年齡或喜愛的動物。
作業
與資料庫的互動。
交易
這是一個作業 (或一組作業) 的包裝函式,可確保資料庫完整性。如果交易中的其中一個動作失敗,系統不會套用這些動作,且資料庫會返回交易開始前的狀態。IndexedDB 中的所有讀取或寫入作業都必須屬於交易的一部分。這可讓您執行不可部分完成的讀取-修改-寫入作業,而不必擔心會同時在資料庫上執行其他執行緒。
遊標
疊代資料庫中多筆記錄的機制。

如何檢查 IndexedDB 支援

索引資料庫幾乎普遍支援索引資料庫。不過,如果您使用的是舊版瀏覽器,就不建議使用功能偵測支援功能。最簡單的方法是檢查 window 物件:

function indexedDBStuff () {
  // Check for IndexedDB support:
  if (!('indexedDB' in window)) {
    // Can't use IndexedDB
    console.log("This browser doesn't support IndexedDB");
    return;
  } else {
    // Do IndexedDB stuff here:
    // ...
  }
}

// Run IndexedDB code:
indexedDBStuff();

如何開啟資料庫

使用 IndexedDB 時,您就可以使用任意名稱建立多個資料庫。嘗試開啟資料庫時,如果資料庫不存在,系統會自動建立該資料庫。如要開啟資料庫,您可以使用 openDB() 方法搭配 idb 程式庫:

import {openDB} from 'idb';

async function useDB () {
  // Returns a promise, which makes `idb` usable with async/await.
  const dbPromise = await openDB('example-database', version, events);
}

useDB();

這個方法會傳回一個承諾,這個會解析為資料庫物件。使用 openDB() 時,您需要提供名稱、版本編號和事件物件來設定資料庫。

以下是結構定義中的 openDB() 方法範例:

import {openDB} from 'idb';

async function useDB () {
  // Opens the first version of the 'test-db1' database.
  // If the database does not exist, it will be created.
  const dbPromise = await openDB('test-db1', 1);
}

useDB();

請將 IndexedDB 支援檢查置於匿名函式頂端。如果瀏覽器不支援 IndexedDB,就會退出函式。接著,您要呼叫 openDB() 方法以開啟名為 'test-db1' 的資料庫。在這個範例中,系統保留了選用的事件物件,以簡化相關流程,不過您最後還是需要指定這個物件,才能使用 IndexedDB 執行任何有意義的工作。

如何使用物件儲存庫

IndexedDB 資料庫包含一或多個物件儲存庫。物件存放區的概念與 SQL 資料庫中的資料表類似。物件儲存庫與 SQL 資料表一樣,包含資料列和資料欄,但在指數資料庫中,索引資料庫物件儲存庫中的欄數和該鍵相關聯的資料欄位數量較少。

建立物件儲存庫

舉例來說,網站會保存使用者個人資料和附註,您可以想像有包含 person 物件的 people 物件儲存庫,以及 notes 物件存放區。結構完善的 IndexedDB 資料庫應針對每種需要保存的資料類型提供一個物件儲存區。

為確保資料庫完整性,只能在 openDB() 呼叫的事件物件中建立和移除物件儲存庫。事件物件會公開 upgrade() 方法,提供建立物件儲存空間的方式。呼叫 upgrade() 方法中的 createObjectStore() 方法來建立物件儲存區:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('example-database', 1, {
    upgrade (db) {
      // Creates an object store:
      db.createObjectStore('storeName', options);
    }
  });
}

createStoreInDB();

這個方法會使用物件存放區的名稱,以及可讓您為物件儲存區定義各種屬性的選用設定物件。

以下是 createObjectStore() 方法的使用範例:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db1', 1, {
    upgrade (db) {
      console.log('Creating a new object store...');

      // Checks if the object store exists:
      if (!db.objectStoreNames.contains('people')) {
        // If the object store does not exist, create it:
        db.createObjectStore('people');
      }
    }
  });
}

createStoreInDB();

在這個範例中,事件物件會傳遞至 openDB() 方法以建立物件儲存庫,和先前一樣,建立物件存放區的工作會在事件物件的 upgrade() 方法中完成。不過,如果您嘗試建立已存在的物件儲存庫,瀏覽器就會擲回錯誤,因此您將 createObjectStore() 方法納入 if 陳述式,藉此檢查物件存放區是否存在。在 if 區塊中,您可以呼叫 createObjectStore() 以建立名為 'firstOS' 的物件儲存區。

如何定義主鍵

定義物件儲存區時,您可以透過主鍵定義在儲存庫中識別資料的方式。您可以定義金鑰路徑或使用金鑰產生器,來定義主鍵。

金鑰路徑是指一律存在且包含不重複值的屬性。舉例來說,如果是 people 物件存放區,您可以選擇電子郵件地址做為金鑰路徑:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        db.createObjectStore('people', { keyPath: 'email' });
      }
    }
  });
}

createStoreInDB();

這個範例會建立名為 'people' 的物件儲存庫,並將 email 屬性指派為 keyPath 選項中的主鍵。

您也可以使用金鑰產生器,例如 autoIncrement。金鑰產生器會為新增至物件儲存庫的每個物件建立專屬值。根據預設,如果您沒有指定鍵,IndexedDB 會建立鍵,並將鍵與資料分開儲存。

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('notes')) {
        db.createObjectStore('notes', { autoIncrement: true });
      }
    }
  });
}

createStoreInDB();

這個範例會建立名為 'notes' 的物件儲存庫,並設定系統自動指派主鍵做為自動遞增編號。

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('logs')) {
        db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createStoreInDB();

這個範例與前述範例類似,但這次自動遞增的值會明確指派給名為 'id' 的屬性。

請根據您的資料選擇要使用哪種方法來定義金鑰。如果資料具有不重複屬性,您可以讓 keyPath 強制要求不重複值。否則,使用自動遞增的值才是合理的。

以下程式碼會建立三個物件儲存庫,示範在物件儲存庫中定義主鍵的各種方式:

import {openDB} from 'idb';

async function createStoresInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        db.createObjectStore('people', { keyPath: 'email' });
      }

      if (!db.objectStoreNames.contains('notes')) {
        db.createObjectStore('notes', { autoIncrement: true });
      }

      if (!db.objectStoreNames.contains('logs')) {
        db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createStoresInDB();

如何定義索引

索引是一種物件儲存區,可以依指定屬性從參照物件儲存庫擷取資料。索引位於參照物件儲存空間內,其中包含相同的資料,但會使用指定屬性做為其鍵路徑,而非參照儲存庫的主鍵。建立物件儲存庫時必須建立索引,並且可用來定義資料的獨特限制。

如要建立索引,請對物件儲存例項呼叫 createIndex() 方法:

import {openDB} from 'idb';

async function createIndexInStore() {
  const dbPromise = await openDB('storeName', 1, {
    upgrade (db) {
      const objectStore = db.createObjectStore('storeName');

      objectStore.createIndex('indexName', 'property', options);
    }
  });
}

createIndexInStore();

這個方法會建立並傳回索引物件。物件儲存庫執行個體的 createIndex() 方法,使用新索引的名稱做為第一個引數,第二個引數則參照您要建立索引之資料上的屬性。最後一個引數可讓您定義兩個選項,決定索引的運作方式:uniquemultiEntry。如果將 unique 設為 true,索引就不會允許單一索引鍵重複值。接著,multiEntry 會決定 createIndex() 在已建立索引的屬性是陣列時的行為。如果設為 truecreateIndex() 會在每個陣列元素的索引中加入項目。否則,系統會新增包含該陣列的單一項目。

範例如下:

import {openDB} from 'idb';

async function createIndexesInStores () {
  const dbPromise = await openDB('test-db3', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        const peopleObjectStore = db.createObjectStore('people', { keyPath: 'email' });

        peopleObjectStore.createIndex('gender', 'gender', { unique: false });
        peopleObjectStore.createIndex('ssn', 'ssn', { unique: true });
      }

      if (!db.objectStoreNames.contains('notes')) {
        const notesObjectStore = db.createObjectStore('notes', { autoIncrement: true });

        notesObjectStore.createIndex('title', 'title', { unique: false });
      }

      if (!db.objectStoreNames.contains('logs')) {
        const logsObjectStore = db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createIndexesInStores();

在這個範例中,'people''notes' 物件儲存庫都有索引。如要建立索引,您必須先將 createObjectStore() (這是物件儲存物件) 的結果指派給變數,以便針對變數呼叫 createIndex()

如何使用資料

本節說明如何建立、讀取、更新及刪除資料。這些作業全都非同步,使用 promise 讓 IndexedDB API 使用要求。這樣可以簡化 API。您不必監聽要求觸發的事件,可以改為在 openDB() 方法傳回的資料庫物件上呼叫 .then(),開始與資料庫互動,或 await 建立資料庫。

IndexedDB 中的所有資料作業都會在交易中執行。每項作業都會形成以下形式:

  1. 取得資料庫物件。
  2. 在資料庫中開啟交易。
  3. 開啟交易的物件存放區。
  4. 在物件存放區執行作業。

交易可視為一種安全的包裝函式,用於作業或一組作業。如果交易中的其中一個動作失敗,所有動作都會復原。交易專屬於您在開啟交易時定義的一或多個物件儲存庫。可以是唯讀或讀取及寫入。這會指出交易中的作業是否讀取資料或變更資料庫。

建立資料

如要建立資料,請對資料庫例項呼叫 add() 方法,然後傳入要新增的資料。add() 方法的第一個引數是要新增資料的物件儲存庫,第二個引數則是包含要新增欄位和相關資料的物件。以下是最簡單的例子,其中會加入單列資料:

import {openDB} from 'idb';

async function addItemToStore () {
  const db = await openDB('example-database', 1);

  await db.add('storeName', {
    field: 'data'
  });
}

addItemToStore();

每次 add() 呼叫都會在交易中發生,因此即使 promise 成功解決,也不代表作業可以執行。請記住,如果交易中的其中一個動作失敗,交易中的所有作業都會復原。

為確保新增作業已完成,您需要使用 transaction.done() 方法檢查整筆交易是否已完成。這會在交易完成時解決,並在交易錯誤時拒絕。請注意,這個方法實際上不會完成交易。交易會自行完成。您必須對所有「寫入」作業執行這項檢查,因為這是您瞭解資料庫變更實際上是否已執行的唯一方法。

以下程式碼示範了 add() 方法的用法,但這次使用交易:

import {openDB} from 'idb';

async function addItemsToStore () {
  const db = await openDB('test-db4', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('foods')) {
        db.createObjectStore('foods', { keyPath: 'name' });
      }
    }
  });
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Add multiple items to the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.add({
      name: 'Sandwich',
      price: 4.99,
      description: 'A very tasty sandwich!',
      created: new Date().getTime(),
    }),
    tx.store.add({
      name: 'Eggs',
      price: 2.99,
      description: 'Some nice eggs you can cook up!',
      created: new Date().getTime(),
    }),
    tx.done
  ]);
}

addItemsToStore();

開啟資料庫 (並視需要建立物件儲存庫) 後,您必須在交易上呼叫 transaction() 方法來開啟交易。此方法會接受要交易的商店及其模式引數。在本範例中,我們希望寫入商店,因此上述範例中指定 'readwrite'

下一步是開始在交易中將商品新增至商店。在上述範例中,我們要處理 'foods' 商店上每個傳回承諾的三項作業:

  1. 新增美味三明治的記錄。
  2. 新增一些雞蛋的記錄。
  3. 表示交易已完成 (tx.done)。

上述所有行動都是經承諾的,我們需要等候所有動作完成。將這些承諾傳遞至 Promise.all,是達成人體工學的絕佳方式。Promise.all 會接受任何 promise 陣列,並在所有承諾都解決後就會結束。

對於要新增的兩個記錄,交易例項的 store 介面具有可呼叫的 add 方法,且資料會傳遞至兩筆記錄。Promise.all 呼叫本身可以 await,並在交易完成時結束。

讀取資料

如要讀取資料,請在使用 openDB() 方法擷取的資料庫例項上呼叫 get() 方法。get() 會擷取商店名稱,以及要從商店擷取的物件主鍵值。基本範例:

import {openDB} from 'idb';

async function getItemFromStore () {
  const db = await openDB('example-database', 1);

  // Get a value from the object store by its primary key value:
  const value = await db.get('storeName', 'unique-primary-key-value');
}

getItemFromStore();

add() 一樣,get() 方法會傳回 promise,並視需要 await 例項;或者,如果不需要,請使用 .then() 回呼的所有承諾產品。

以下範例在 'test-db4' 資料庫的 'foods' 物件儲存庫中使用 get() 方法,透過 'name' 主鍵取得單一資料列:

import {openDB} from 'idb';

async function getItemFromStore () {
  const db = await openDB('test-db4', 1);
  const value = await db.get('foods', 'Sandwich');

  console.dir(value);
}

getItemFromStore();

如要從資料庫擷取單一資料列,做法很簡單:開啟資料庫,然後指定要取得資料的資料列物件儲存庫和主鍵值。由於 get() 方法會傳回承諾,因此您可以 await

更新資料

如要更新資料,請呼叫物件存放區的 put() 方法。put() 方法類似於 add() 方法,也可以用來取代 add(),以便在物件存放區中建立資料。以下是使用 put() 依據主鍵值更新物件儲存庫中資料列的最簡單範例:

import {openDB} from 'idb';

async function updateItemInStore () {
  const db = await openDB('example-database', 1);

  // Update a value from in an object store with an in-line key:
  await db.put('storeName', { inlineKeyName: 'newValue' });

  // Update a value from in an object store with an out-of-line key.
  // In this case, the out-of-line key value is 1, which is the
  // auto-incremented value.
  await db.put('otherStoreName', { field: 'value' }, 1);
}

updateItemInStore();

如同其他方法,這個方法會傳回 promise。您也可以使用 put() 做為交易的一部分,就跟使用 add() 方法一樣。以下為使用前述的 'foods' 商店範例,但我們只更新三明治和雞蛋的價格:

import {openDB} from 'idb';

async function updateItemsInStore () {
  const db = await openDB('test-db4', 1);
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Update multiple items in the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.put({
      name: 'Sandwich',
      price: 5.99,
      description: 'A MORE tasty sandwich!',
      updated: new Date().getTime() // This creates a new field
    }),
    tx.store.put({
      name: 'Eggs',
      price: 3.99,
      description: 'Some even NICER eggs you can cook up!',
      updated: new Date().getTime() // This creates a new field
    }),
    tx.done
  ]);
}

updateItemsInStore();

項目的更新方式取決於您設定金鑰的方式。如果您設定 keyPath,物件儲存庫中的每一列都會與所謂的內嵌索引鍵建立關聯。上例會根據此鍵更新資料列,而在這種情況下,您在更新資料列時,必須指定該鍵,物件儲存庫中的適當項目才會實際更新。將 autoIncrement 設為主鍵即可建立非行鍵

刪除資料

如要刪除資料,請呼叫物件儲存庫上的 delete() 方法:

import {openDB} from 'idb';

async function deleteItemFromStore () {
  const db = await openDB('example-database', 1);

  // Delete a value 
  await db.delete('storeName', 'primary-key-value');
}

deleteItemFromStore();

add()put() 一樣,這個項目也可在交易中使用:

import {openDB} from 'idb';

async function deleteItemsFromStore () {
  const db = await openDB('test-db4', 1);
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Delete multiple items from the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.delete('Sandwich'),
    tx.store.delete('Eggs'),
    tx.done
  ]);
}

deleteItemsFromStore();

資料庫互動的結構與其他作業相同。請注意,在傳遞至 Promise.all 的陣列中加入 tx.done 方法,確認已執行刪除作業,再次檢查整項交易是否已完成。

取得所有資料

目前,您一次只能從商店擷取一個物件。您也可以使用 getAll() 方法或使用遊標,擷取物件儲存庫或索引中的所有資料 (或部分資料)。

使用 getAll() 方法

如要擷取物件存放區的所有資料,最簡單的方法是在物件儲存庫或索引上呼叫 getAll() 方法,如下所示:

import {openDB} from 'idb';

async function getAllItemsFromStore () {
  const db = await openDB('test-db4', 1);

  // Get all values from the designated object store:
  const allValues = await db.getAll('storeName');

  console.dir(allValues);
}

getAllItemsFromStore();

這個方法會傳回物件儲存庫中的所有物件,但不會有任何限制。這是從物件儲存庫取得所有值最直接的方法,但也最靈活有彈性。

import {openDB} from 'idb';

async function getAllItemsFromStore () {
  const db = await openDB('test-db4', 1);

  // Get all values from the designated object store:
  const allValues = await db.getAll('foods');

  console.dir(allValues);
}

getAllItemsFromStore();

這裡的 'foods' 物件存放區會呼叫 getAll()。這樣做會傳回按主鍵排序的儲存庫 'foods' 中的所有物件。

如何使用遊標

如果想擷取所有資料,另一種方法就是使用遊標,而不是一次取得所有資料。遊標會逐一選取物件儲存庫中的各個物件或逐一建立索引,方便您在選取資料的同時處理選取項目。遊標就像其他資料庫作業一樣,會在交易內運作。

您可以針對物件存放區呼叫 openCursor() 方法來建立遊標。這項作業會在交易過程中完成。使用上一個範例的 'foods' 儲存庫,即可將遊標導向物件儲存庫中的所有資料列:

import {openDB} from 'idb';

async function getAllItemsFromStoreWithCursor () {
  const db = await openDB('test-db4', 1);
  const tx = await db.transaction('foods', 'readonly');

  // Open a cursor on the designated object store:
  let cursor = await tx.store.openCursor();

  // Iterate on the cursor, row by row:
  while (cursor) {
    // Show the data in the row at the current cursor position:
    console.log(cursor.key, cursor.value);

    // Advance the cursor to the next row:
    cursor = await cursor.continue();
  }
}

getAllItemsFromStoreWithCursor();

在此情況下,交易會以 'readonly' 模式開啟,並呼叫其 openCursor 方法。在後續的 while 迴圈中,遊標目前位置的資料列可以讀取其 keyvalue 屬性,而且您可以使用最適合應用程式的方式操作這些值。準備就緒後,您可以呼叫 cursor 物件的 continue() 方法前往下一個資料列,且 while 迴圈會在達到資料集結尾時終止。

如何將遊標與範圍和索引搭配使用

你可以透過幾種不同的方式取得所有資料,但如果你只想根據特定資源取得部分資料,該怎麼做?這是索引的來源。索引可讓您依據主鍵以外的屬性,擷取物件儲存空間中的資料。您可以在任何屬性上建立索引 (會成為索引的 keyPath)、指定該屬性的範圍,並使用 getAll() 方法或遊標取得範圍內的資料。

請使用 IDBKeyRange 物件定義範圍。這個物件提供五個方法,用來定義範圍的限制:

一如預期,upperBound()lowerBound() 方法會指定範圍的上限與下限。

IDBKeyRange.lowerBound(indexKey);

或:

IDBKeyRange.upperBound(indexKey);

每個函式都會接受一個引數,也就是您要指定為上限或下限的項目索引的 keyPath 值。

bound() 方法用於指定上限和下限,並以下限值做為第一個引數:

IDBKeyRange.bound(lowerIndexKey, upperIndexKey);

這些函式的範圍預設為包含,但可以指定為排除範圍,方法是將 true 做為第二個引數傳遞 (若為 bound(),則將值設為最低和上限的第三和第四個引數)。包含範圍限制的資料。排除範圍則無效。

讓我們來看看下面這個例子。在這個示範中,您已在 'foods' 物件儲存庫的 'price' 屬性中建立索引。並新增了具有範圍上限和兩個輸入內容的小表單。假設您要傳入函式中下限和上限,做為代表價格的浮點數:

import {openDB} from 'idb';

async function searchItems (lower, upper) {
  if (!lower === '' && upper === '') {
    return;
  }

  let range;

  if (lower !== '' && upper !== '') {
    range = IDBKeyRange.bound(lower, upper);
  } else if (lower === '') {
    range = IDBKeyRange.upperBound(upper);
  } else {
    range = IDBKeyRange.lowerBound(lower);
  }

  const db = await openDB('test-db4', 1);
  const tx = await db.transaction('foods', 'readonly');
  const index = tx.store.index('price');

  // Open a cursor on the designated object store:
  let cursor = await index.openCursor(range);

  if (!cursor) {
    return;
  }

  // Iterate on the cursor, row by row:
  while (cursor) {
    // Show the data in the row at the current cursor position:
    console.log(cursor.key, cursor.value);

    // Advance the cursor to the next row:
    cursor = await cursor.continue();
  }
}

// Get items priced between one and four dollars:
searchItems(1.00, 4.00);

程式碼會先取得限制的值,並檢查限制是否存在。接下來的程式碼區塊會決定要使用哪種方法根據值來限制範圍。在資料庫互動中,照常開啟交易中的物件儲存庫,然後在物件儲存庫中開啟 'price' 索引。'price' 索引可讓您依價格搜尋商品。

接著,在索引上開啟遊標,並傳遞該範圍。遊標現在會傳回 promise,代表範圍內的第一個物件,如果範圍內沒有資料,則傳回 undefinedcursor.continue() 方法會傳回代表下一個物件的遊標,以此類推,直到到達範圍結尾為止。

使用資料庫版本管理

呼叫 openDB() 方法時,您可以在第二個參數中指定資料庫版本號碼。在本指南的所有範例中,版本已設為 1,但如果您需要某種方式進行修改,可以將資料庫升級至新版本。如果指定版本高於現有資料庫的版本,系統會執行事件物件中的 upgrade 回呼,以便您將新的物件儲存及索引新增至資料庫。

upgrade 回呼中的 db 物件具有特殊的 oldVersion 屬性,代表瀏覽器中現有的資料庫目前版本號碼。您可以將這個版本編號傳遞至 switch 陳述式,根據現有的資料庫版本號碼,執行 upgrade 回呼中的程式碼區塊。範例如下:

import {openDB} from 'idb';

const db = await openDB('example-database', 2, {
  upgrade (db, oldVersion) {
    switch (oldVersion) {
      case 0:
        // Create first object store:
        db.createObjectStore('store', { keyPath: 'name' });

      case 1:
        // Get the original object store, and create an index on it:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('name', 'name');
    }
  }
});

以下範例會將資料庫的最新版本設為 2。當此程式碼首次執行時,由於瀏覽器中尚不存在資料庫,oldVersion0,而 switch 陳述式從 case 0 開始。在本範例中,這會將 'store' 物件儲存庫加入資料庫。

如要在 'store' 物件儲存庫中建立 'description' 索引,請更新版本號碼並新增 case 區塊,如下所示:

import {openDB} from 'idb';

const db = await openDB('example-database', 3, {
  upgrade (db, oldVersion) {
    switch (oldVersion) {
      case 0:
        // Create first object store:
        db.createObjectStore('store', { keyPath: 'name' });

      case 1:
        // Get the original object store, and create an index on it:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('name', 'name');

      case 2:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('description', 'description');
    }
  }
});

假設您在上一個範例中建立的資料庫仍在瀏覽器中,而當執行 oldVersion 時,瀏覽器內依然存在 2。系統會略過 case 0case 1,且瀏覽器會在 case 2 中執行程式碼,進而建立 'description' 索引。完成所有操作後,瀏覽器就會建立位於第 3 版的資料庫,其中包含了 'name''description' 索引的 'store' 物件儲存庫。

其他資訊

使用 IndexedDB 時,可參考下列資源提供更多資訊和背景資訊。

IndexedDB 說明文件

資料儲存限制