# IndexedDB API
## 概述
隨著瀏覽器的功能不斷增強,越來越多的網站開始考慮,將大量數據儲存在客戶端,這樣可以減少從服務器獲取數據,直接從本地獲取數據。
現有的瀏覽器數據儲存方案,都不適合儲存大量數據:Cookie 的大小不超過 4KB,且每次請求都會發送回服務器;LocalStorage 在 2.5MB 到 10MB 之間(各家瀏覽器不同),而且不提供搜索功能,不能建立自定義的索引。所以,需要一種新的解決方案,這就是 IndexedDB 誕生的背景。
通俗地說,IndexedDB 就是瀏覽器提供的本地數據庫,它可以被網頁腳本創建和操作。IndexedDB 允許儲存大量數據,提供查找接口,還能建立索引。這些都是 LocalStorage 所不具備的。就數據庫類型而言,IndexedDB 不屬于關系型數據庫(不支持 SQL 查詢語句),更接近 NoSQL 數據庫。
IndexedDB 具有以下特點。
**(1)鍵值對儲存。** IndexedDB 內部采用對象倉庫(object store)存放數據。所有類型的數據都可以直接存入,包括 JavaScript 對象。對象倉庫中,數據以“鍵值對”的形式保存,每一個數據記錄都有對應的主鍵,主鍵是獨一無二的,不能有重復,否則會拋出一個錯誤。
**(2)異步。** IndexedDB 操作時不會鎖死瀏覽器,用戶依然可以進行其他操作,這與 LocalStorage 形成對比,后者的操作是同步的。異步設計是為了防止大量數據的讀寫,拖慢網頁的表現。
**(3)支持事務。** IndexedDB 支持事務(transaction),這意味著一系列操作步驟之中,只要有一步失敗,整個事務就都取消,數據庫回滾到事務發生之前的狀態,不存在只改寫一部分數據的情況。
**(4)同源限制。** IndexedDB 受到同源限制,每一個數據庫對應創建它的域名。網頁只能訪問自身域名下的數據庫,而不能訪問跨域的數據庫。
**(5)儲存空間大。** IndexedDB 的儲存空間比 LocalStorage 大得多,一般來說不少于 250MB,甚至沒有上限。
**(6)支持二進制儲存。** IndexedDB 不僅可以儲存字符串,還可以儲存二進制數據(ArrayBuffer 對象和 Blob 對象)。
## 基本概念
IndexedDB 是一個比較復雜的 API,涉及不少概念。它把不同的實體,抽象成一個個對象接口。學習這個 API,就是學習它的各種對象接口。
- 數據庫:IDBDatabase 對象
- 對象倉庫:IDBObjectStore 對象
- 索引: IDBIndex 對象
- 事務: IDBTransaction 對象
- 操作請求:IDBRequest 對象
- 指針: IDBCursor 對象
- 主鍵集合:IDBKeyRange 對象
下面是一些主要的概念。
**(1)數據庫**
數據庫是一系列相關數據的容器。每個域名(嚴格的說,是協議 + 域名 + 端口)都可以新建任意多個數據庫。
IndexedDB 數據庫有版本的概念。同一個時刻,只能有一個版本的數據庫存在。如果要修改數據庫結構(新增或刪除表、索引或者主鍵),只能通過升級數據庫版本完成。
**(2)對象倉庫**
每個數據庫包含若干個對象倉庫(object store)。它類似于關系型數據庫的表格。
**(3)數據記錄**
對象倉庫保存的是數據記錄。每條記錄類似于關系型數據庫的行,但是只有主鍵和數據體兩部分。主鍵用來建立默認的索引,必須是不同的,否則會報錯。主鍵可以是數據記錄里面的一個屬性,也可以指定為一個遞增的整數編號。
```javascript
{ id: 1, text: 'foo' }
```
上面的對象中,`id`屬性可以當作主鍵。
數據體可以是任意數據類型,不限于對象。
**(4)索引**
為了加速數據的檢索,可以在對象倉庫里面,為不同的屬性建立索引。
**(5)事務**
數據記錄的讀寫和刪改,都要通過事務完成。事務對象提供`error`、`abort`和`complete`三個事件,用來監聽操作結果。
## 操作流程
IndexedDB 數據庫的各種操作,一般是按照下面的流程進行的。這個部分只給出簡單的代碼示例,用于快速上手,詳細的各個對象的 API 放在后文介紹。
### 打開數據庫
使用 IndexedDB 的第一步是打開數據庫,使用`indexedDB.open()`方法。
```javascript
var request = window.indexedDB.open(databaseName, version);
```
這個方法接受兩個參數,第一個參數是字符串,表示數據庫的名字。如果指定的數據庫不存在,就會新建數據庫。第二個參數是整數,表示數據庫的版本。如果省略,打開已有數據庫時,默認為當前版本;新建數據庫時,默認為`1`。
`indexedDB.open()`方法返回一個 IDBRequest 對象。這個對象通過三種事件`error`、`success`、`upgradeneeded`,處理打開數據庫的操作結果。
**(1)error 事件**
`error`事件表示打開數據庫失敗。
```javascript
request.onerror = function (event) {
console.log('數據庫打開報錯');
};
```
**(2)success 事件**
`success`事件表示成功打開數據庫。
```javascript
var db;
request.onsuccess = function (event) {
db = request.result;
console.log('數據庫打開成功');
};
```
這時,通過`request`對象的`result`屬性拿到數據庫對象。
**(3)upgradeneeded 事件**
如果指定的版本號,大于數據庫的實際版本號,就會發生數據庫升級事件`upgradeneeded`。
```javascript
var db;
request.onupgradeneeded = function (event) {
db = event.target.result;
}
```
這時通過事件對象的`target.result`屬性,拿到數據庫實例。
### 新建數據庫
新建數據庫與打開數據庫是同一個操作。如果指定的數據庫不存在,就會新建。不同之處在于,后續的操作主要在`upgradeneeded`事件的監聽函數里面完成,因為這時版本從無到有,所以會觸發這個事件。
通常,新建數據庫以后,第一件事是新建對象倉庫(即新建表)。
```javascript
request.onupgradeneeded = function(event) {
db = event.target.result;
var objectStore = db.createObjectStore('person', { keyPath: 'id' });
}
```
上面代碼中,數據庫新建成功以后,新增一張叫做`person`的表格,主鍵是`id`。
更好的寫法是先判斷一下,這張表格是否存在,如果不存在再新建。
```javascript
request.onupgradeneeded = function (event) {
db = event.target.result;
var objectStore;
if (!db.objectStoreNames.contains('person')) {
objectStore = db.createObjectStore('person', { keyPath: 'id' });
}
}
```
主鍵(key)是默認建立索引的屬性。比如,數據記錄是`{ id: 1, name: '張三' }`,那么`id`屬性可以作為主鍵。主鍵也可以指定為下一層對象的屬性,比如`{ foo: { bar: 'baz' } }`的`foo.bar`也可以指定為主鍵。
如果數據記錄里面沒有合適作為主鍵的屬性,那么可以讓 IndexedDB 自動生成主鍵。
```javascript
var objectStore = db.createObjectStore(
'person',
{ autoIncrement: true }
);
```
上面代碼中,指定主鍵為一個遞增的整數。
新建對象倉庫以后,下一步可以新建索引。
```javascript
request.onupgradeneeded = function(event) {
db = event.target.result;
var objectStore = db.createObjectStore('person', { keyPath: 'id' });
objectStore.createIndex('name', 'name', { unique: false });
objectStore.createIndex('email', 'email', { unique: true });
}
```
上面代碼中,`IDBObject.createIndex()`的三個參數分別為索引名稱、索引所在的屬性、配置對象(說明該屬性是否包含重復的值)。
### 新增數據
新增數據指的是向對象倉庫寫入數據記錄。這需要通過事務完成。
```javascript
function add() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.add({ id: 1, name: '張三', age: 24, email: 'zhangsan@example.com' });
request.onsuccess = function (event) {
console.log('數據寫入成功');
};
request.onerror = function (event) {
console.log('數據寫入失敗');
}
}
add();
```
上面代碼中,寫入數據需要新建一個事務。新建時必須指定表格名稱和操作模式(“只讀”或“讀寫”)。新建事務以后,通過`IDBTransaction.objectStore(name)`方法,拿到 IDBObjectStore 對象,再通過表格對象的`add()`方法,向表格寫入一條記錄。
寫入操作是一個異步操作,通過監聽連接對象的`success`事件和`error`事件,了解是否寫入成功。
### 讀取數據
讀取數據也是通過事務完成。
```javascript
function read() {
var transaction = db.transaction(['person']);
var objectStore = transaction.objectStore('person');
var request = objectStore.get(1);
request.onerror = function(event) {
console.log('事務失敗');
};
request.onsuccess = function( event) {
if (request.result) {
console.log('Name: ' + request.result.name);
console.log('Age: ' + request.result.age);
console.log('Email: ' + request.result.email);
} else {
console.log('未獲得數據記錄');
}
};
}
read();
```
上面代碼中,`objectStore.get()`方法用于讀取數據,參數是主鍵的值。
### 遍歷數據
遍歷數據表格的所有記錄,要使用指針對象 IDBCursor。
```javascript
function readAll() {
var objectStore = db.transaction('person').objectStore('person');
objectStore.openCursor().onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
console.log('Id: ' + cursor.key);
console.log('Name: ' + cursor.value.name);
console.log('Age: ' + cursor.value.age);
console.log('Email: ' + cursor.value.email);
cursor.continue();
} else {
console.log('沒有更多數據了!');
}
};
}
readAll();
```
上面代碼中,新建指針對象的`openCursor()`方法是一個異步操作,所以要監聽`success`事件。
### 更新數據
更新數據要使用`IDBObject.put()`方法。
```javascript
function update() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.put({ id: 1, name: '李四', age: 35, email: 'lisi@example.com' });
request.onsuccess = function (event) {
console.log('數據更新成功');
};
request.onerror = function (event) {
console.log('數據更新失敗');
}
}
update();
```
上面代碼中,`put()`方法自動更新了主鍵為`1`的記錄。
### 刪除數據
`IDBObjectStore.delete()`方法用于刪除記錄。
```javascript
function remove() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.delete(1);
request.onsuccess = function (event) {
console.log('數據刪除成功');
};
}
remove();
```
### 使用索引
索引的意義在于,可以讓你搜索任意字段,也就是說從任意字段拿到數據記錄。如果不建立索引,默認只能搜索主鍵(即從主鍵取值)。
假定新建表格的時候,對`name`字段建立了索引。
```javascript
objectStore.createIndex('name', 'name', { unique: false });
```
現在,就可以從`name`找到對應的數據記錄了。
```javascript
var transaction = db.transaction(['person'], 'readonly');
var store = transaction.objectStore('person');
var index = store.index('name');
var request = index.get('李四');
request.onsuccess = function (e) {
var result = e.target.result;
if (result) {
// ...
} else {
// ...
}
}
```
## indexedDB 對象
瀏覽器原生提供`indexedDB`對象,作為開發者的操作接口。
### indexedDB.open()
`indexedDB.open()`方法用于打開數據庫。這是一個異步操作,但是會立刻返回一個 IDBOpenDBRequest 對象。
```javascript
var openRequest = window.indexedDB.open('test', 1);
```
上面代碼表示,打開一個名為`test`、版本為`1`的數據庫。如果該數據庫不存在,則會新建該數據庫。
`open()`方法的第一個參數是數據庫名稱,格式為字符串,不可省略;第二個參數是數據庫版本,是一個大于`0`的正整數(`0`將報錯),如果該參數大于當前版本,會觸發數據庫升級。第二個參數可省略,如果數據庫已存在,將打開當前版本的數據庫;如果數據庫不存在,將創建該版本的數據庫,默認版本為`1`。
打開數據庫是異步操作,通過各種事件通知客戶端。下面是有可能觸發的4種事件。
- **success**:打開成功。
- **error**:打開失敗。
- **upgradeneeded**:第一次打開該數據庫,或者數據庫版本發生變化。
- **blocked**:上一次的數據庫連接還未關閉。
第一次打開數據庫時,會先觸發`upgradeneeded`事件,然后觸發`success`事件。
根據不同的需要,對上面4種事件監聽函數。
```javascript
var openRequest = indexedDB.open('test', 1);
var db;
openRequest.onupgradeneeded = function (e) {
console.log('Upgrading...');
}
openRequest.onsuccess = function (e) {
console.log('Success!');
db = openRequest.result;
}
openRequest.onerror = function (e) {
console.log('Error');
console.log(e);
}
```
上面代碼有兩個地方需要注意。首先,`open()`方法返回的是一個對象(IDBOpenDBRequest),監聽函數就定義在這個對象上面。其次,`success`事件發生后,從`openRequest.result`屬性可以拿到已經打開的`IndexedDB`數據庫對象。
### indexedDB.deleteDatabase()
`indexedDB.deleteDatabase()`方法用于刪除一個數據庫,參數為數據庫的名字。它會立刻返回一個`IDBOpenDBRequest`對象,然后對數據庫執行異步刪除。刪除操作的結果會通過事件通知,`IDBOpenDBRequest`對象可以監聽以下事件。
- `success`:刪除成功
- `error`:刪除報錯
```javascript
var DBDeleteRequest = window.indexedDB.deleteDatabase('demo');
DBDeleteRequest.onerror = function (event) {
console.log('Error');
};
DBDeleteRequest.onsuccess = function (event) {
console.log('success');
};
```
調用`deleteDatabase()`方法以后,當前數據庫的其他已經打開的連接都會接收到`versionchange`事件。
注意,刪除不存在的數據庫并不會報錯。
### indexedDB.cmp()
`indexedDB.cmp()`方法比較兩個值是否為 indexedDB 的相同的主鍵。它返回一個整數,表示比較的結果:`0`表示相同,`1`表示第一個主鍵大于第二個主鍵,`-1`表示第一個主鍵小于第二個主鍵。
```javascript
window.indexedDB.cmp(1, 2) // -1
```
注意,這個方法不能用來比較任意的 JavaScript 值。如果參數是布爾值或對象,它會報錯。
```javascript
window.indexedDB.cmp(1, true) // 報錯
window.indexedDB.cmp({}, {}) // 報錯
```
## IDBRequest 對象
IDBRequest 對象表示打開的數據庫連接,`indexedDB.open()`方法和`indexedDB.deleteDatabase()`方法會返回這個對象。數據庫的操作都是通過這個對象完成的。
這個對象的所有操作都是異步操作,要通過`readyState`屬性判斷是否完成,如果為`pending`就表示操作正在進行,如果為`done`就表示操作完成,可能成功也可能失敗。
操作完成以后,觸發`success`事件或`error`事件,這時可以通過`result`屬性和`error`屬性拿到操作結果。如果在`pending`階段,就去讀取這兩個屬性,是會報錯的。
IDBRequest 對象有以下屬性。
- `IDBRequest.readyState`:等于`pending`表示操作正在進行,等于`done`表示操作正在完成。
- `IDBRequest.result`:返回請求的結果。如果請求失敗、結果不可用,讀取該屬性會報錯。
- `IDBRequest.error`:請求失敗時,返回錯誤對象。
- `IDBRequest.source`:返回請求的來源(比如索引對象或 ObjectStore)。
- `IDBRequest.transaction`:返回當前請求正在進行的事務,如果不包含事務,返回`null`。
- `IDBRequest.onsuccess`:指定`success`事件的監聽函數。
- `IDBRequest.onerror`:指定`error`事件的監聽函數。
IDBOpenDBRequest 對象繼承了 IDBRequest 對象,提供了兩個額外的事件監聽屬性。
- `IDBOpenDBRequest.onblocked`:指定`blocked`事件(`upgradeneeded`事件觸發時,數據庫仍然在使用)的監聽函數。
- `IDBOpenDBRequest.onupgradeneeded`:`upgradeneeded`事件的監聽函數。
## IDBDatabase 對象
打開數據成功以后,可以從`IDBOpenDBRequest`對象的`result`屬性上面,拿到一個`IDBDatabase`對象,它表示連接的數據庫。后面對數據庫的操作,都通過這個對象完成。
```javascript
var db;
var DBOpenRequest = window.indexedDB.open('demo', 1);
DBOpenRequest.onerror = function (event) {
console.log('Error');
};
DBOpenRequest.onsuccess = function(event) {
db = DBOpenRequest.result;
// ...
};
```
### 屬性
IDBDatabase 對象有以下屬性。
- `IDBDatabase.name`:字符串,數據庫名稱。
- `IDBDatabase.version`:整數,數據庫版本。數據庫第一次創建時,該屬性為空字符串。
- `IDBDatabase.objectStoreNames`:DOMStringList 對象(字符串的集合),包含當前數據的所有 object store 的名字。
- `IDBDatabase.onabort`:指定 abort 事件(事務中止)的監聽函數。
- `IDBDatabase.onclose`:指定 close 事件(數據庫意外關閉)的監聽函數。
- `IDBDatabase.onerror`:指定 error 事件(訪問數據庫失敗)的監聽函數。
- `IDBDatabase.onversionchange`:數據庫版本變化時觸發(發生`upgradeneeded`事件,或調用`indexedDB.deleteDatabase()`)。
下面是`objectStoreNames`屬性的例子。該屬性返回一個 DOMStringList 對象,包含了當前數據庫所有對象倉庫的名稱(即表名),可以使用 DOMStringList 對象的`contains`方法,檢查數據庫是否包含某個對象倉庫。
```javascript
if (!db.objectStoreNames.contains('firstOS')) {
db.createObjectStore('firstOS');
}
```
上面代碼先判斷某個對象倉庫是否存在,如果不存在就創建該對象倉庫。
### 方法
IDBDatabase 對象有以下方法。
- `IDBDatabase.close()`:關閉數據庫連接,實際會等所有事務完成后再關閉。
- `IDBDatabase.createObjectStore()`:創建存放數據的對象倉庫,類似于傳統關系型數據庫的表格,返回一個 IDBObjectStore 對象。該方法只能在`versionchange`事件監聽函數中調用。
- `IDBDatabase.deleteObjectStore()`:刪除指定的對象倉庫。該方法只能在`versionchange`事件監聽函數中調用。
- `IDBDatabase.transaction()`:返回一個 IDBTransaction 事務對象。
下面是`createObjectStore()`方法的例子。
```javascript
var request = window.indexedDB.open('demo', 2);
request.onupgradeneeded = function (event) {
var db = event.target.result;
db.onerror = function(event) {
console.log('error');
};
var objectStore = db.createObjectStore('items');
// ...
};
```
上面代碼創建了一個名為`items`的對象倉庫,如果該對象倉庫已經存在,就會拋出一個錯誤。為了避免出錯,需要用到下文的`objectStoreNames`屬性,檢查已有哪些對象倉庫。
`createObjectStore()`方法還可以接受第二個對象參數,用來設置對象倉庫的屬性。
```javascript
db.createObjectStore('test', { keyPath: 'email' });
db.createObjectStore('test2', { autoIncrement: true });
```
上面代碼中,`keyPath`屬性表示主鍵(由于主鍵的值不能重復,所以上例存入之前,必須保證數據的`email`屬性值都是不一樣的),默認值為`null`;`autoIncrement`屬性表示,是否使用自動遞增的整數作為主鍵(第一個數據記錄為1,第二個數據記錄為2,以此類推),默認為`false`。一般來說,`keyPath`和`autoIncrement`屬性只要使用一個就夠了,如果兩個同時使用,表示主鍵為遞增的整數,且對象不得缺少`keyPath`指定的屬性。
下面是`deleteObjectStore()`方法的例子。
```javascript
var dbName = 'sampleDB';
var dbVersion = 2;
var request = indexedDB.open(dbName, dbVersion);
request.onupgradeneeded = function(e) {
var db = request.result;
if (e.oldVersion < 1) {
db.createObjectStore('store1');
}
if (e.oldVersion < 2) {
db.deleteObjectStore('store1');
db.createObjectStore('store2');
}
// ...
};
```
下面是`transaction()`方法的例子,該方法用于創建一個數據庫事務,返回一個 IDBTransaction 對象。向數據庫添加數據之前,必須先創建數據庫事務。
```javascript
var t = db.transaction(['items'], 'readwrite');
```
`transaction()`方法接受兩個參數:第一個參數是一個數組,里面是所涉及的對象倉庫,通常是只有一個;第二個參數是一個表示操作類型的字符串。目前,操作類型只有兩種:`readonly`(只讀)和`readwrite`(讀寫)。添加數據使用`readwrite`,讀取數據使用`readonly`。第二個參數是可選的,省略時默認為`readonly`模式。
## IDBObjectStore 對象
IDBObjectStore 對象對應一個對象倉庫(object store)。`IDBDatabase.createObjectStore()`方法返回的就是一個 IDBObjectStore 對象。
IDBDatabase 對象的`transaction()`返回一個事務對象,該對象的`objectStore()`方法返回 IDBObjectStore 對象,因此可以采用下面的鏈式寫法。
```javascript
db.transaction(['test'], 'readonly')
.objectStore('test')
.get(X)
.onsuccess = function (e) {}
```
### 屬性
IDBObjectStore 對象有以下屬性。
- `IDBObjectStore.indexNames`:返回一個類似數組的對象(DOMStringList),包含了當前對象倉庫的所有索引。
- `IDBObjectStore.keyPath`:返回當前對象倉庫的主鍵。
- `IDBObjectStore.name`:返回當前對象倉庫的名稱。
- `IDBObjectStore.transaction`:返回當前對象倉庫所屬的事務對象。
- `IDBObjectStore.autoIncrement`:布爾值,表示主鍵是否會自動遞增。
### 方法
IDBObjectStore 對象有以下方法。
**(1)IDBObjectStore.add()**
`IDBObjectStore.add()`用于向對象倉庫添加數據,返回一個 IDBRequest 對象。該方法只用于添加數據,如果主鍵相同會報錯,因此更新數據必須使用`put()`方法。
```javascript
objectStore.add(value, key)
```
該方法接受兩個參數,第一個參數是鍵值,第二個參數是主鍵,該參數可選,如果省略默認為`null`。
創建事務以后,就可以獲取對象倉庫,然后使用`add()`方法往里面添加數據了。
```javascript
var db;
var DBOpenRequest = window.indexedDB.open('demo', 1);
DBOpenRequest.onsuccess = function (event) {
db = DBOpenRequest.result;
var transaction = db.transaction(['items'], 'readwrite');
transaction.oncomplete = function (event) {
console.log('transaction success');
};
transaction.onerror = function (event) {
console.log('transaction error: ' + transaction.error);
};
var objectStore = transaction.objectStore('items');
var objectStoreRequest = objectStore.add({ foo: 1 });
objectStoreRequest.onsuccess = function (event) {
console.log('add data success');
};
};
```
**(2)IDBObjectStore.put()**
`IDBObjectStore.put()`方法用于更新某個主鍵對應的數據記錄,如果對應的鍵值不存在,則插入一條新的記錄。該方法返回一個 IDBRequest 對象。
```javascript
objectStore.put(item, key)
```
該方法接受兩個參數,第一個參數為新數據,第二個參數為主鍵,該參數可選,且只在自動遞增時才有必要提供,因為那時主鍵不包含在數據值里面。
**(3)IDBObjectStore.clear()**
`IDBObjectStore.clear()`刪除當前對象倉庫的所有記錄。該方法返回一個 IDBRequest 對象。
```javascript
objectStore.clear()
```
該方法不需要參數。
**(4)IDBObjectStore.delete()**
`IDBObjectStore.delete()`方法用于刪除指定主鍵的記錄。該方法返回一個 IDBRequest 對象。
```javascript
objectStore.delete(Key)
```
該方法的參數為主鍵的值。
**(5)IDBObjectStore.count()**
`IDBObjectStore.count()`方法用于計算記錄的數量。該方法返回一個 IDBRequest 對象。
```javascript
IDBObjectStore.count(key)
```
不帶參數時,該方法返回當前對象倉庫的所有記錄數量。如果主鍵或 IDBKeyRange 對象作為參數,則返回對應的記錄數量。
**(6)IDBObjectStore.getKey()**
`IDBObjectStore.getKey()`用于獲取主鍵。該方法返回一個 IDBRequest 對象。
```javascript
objectStore.getKey(key)
```
該方法的參數可以是主鍵值或 IDBKeyRange 對象。
**(7)IDBObjectStore.get()**
`IDBObjectStore.get()`用于獲取主鍵對應的數據記錄。該方法返回一個 IDBRequest 對象。
```javascript
objectStore.get(key)
```
**(8)IDBObjectStore.getAll()**
`DBObjectStore.getAll()`用于獲取對象倉庫的記錄。該方法返回一個 IDBRequest 對象。
```javascript
// 獲取所有記錄
objectStore.getAll()
// 獲取所有符合指定主鍵或 IDBKeyRange 的記錄
objectStore.getAll(query)
// 指定獲取記錄的數量
objectStore.getAll(query, count)
```
**(9)IDBObjectStore.getAllKeys()**
`IDBObjectStore.getAllKeys()`用于獲取所有符合條件的主鍵。該方法返回一個 IDBRequest 對象。
```javascript
// 獲取所有記錄的主鍵
objectStore.getAllKeys()
// 獲取所有符合條件的主鍵
objectStore.getAllKeys(query)
// 指定獲取主鍵的數量
objectStore.getAllKeys(query, count)
```
**(10)IDBObjectStore.index()**
`IDBObjectStore.index()`方法返回指定名稱的索引對象 IDBIndex。
```javascript
objectStore.index(name)
```
有了索引以后,就可以針對索引所在的屬性讀取數據。
```javascript
var t = db.transaction(['people'], 'readonly');
var store = t.objectStore('people');
var index = store.index('name');
var request = index.get('foo');
```
上面代碼打開對象倉庫以后,先用`index()`方法指定獲取`name`屬性的索引,然后用`get()`方法讀取某個`name`屬性(`foo`)對應的數據。如果`name`屬性不是對應唯一值,這時`get()`方法有可能取回多個數據對象。另外,`get()`是異步方法,讀取成功以后,只能在`success`事件的監聽函數中處理數據。
**(11)IDBObjectStore.createIndex()**
`IDBObjectStore.createIndex()`方法用于新建當前數據庫的一個索引。該方法只能在`VersionChange`監聽函數里面調用。
```javascript
objectStore.createIndex(indexName, keyPath, objectParameters)
```
該方法可以接受三個參數。
- indexName:索引名
- keyPath:主鍵
- objectParameters:配置對象(可選)
第三個參數可以配置以下屬性。
- unique:如果設為`true`,將不允許重復的值
- multiEntry:如果設為`true`,對于有多個值的主鍵數組,每個值將在索引里面新建一個條目,否則主鍵數組對應一個條目。
假定對象倉庫中的數據記錄都是如下的`person`類型。
```javascript
var person = {
name: name,
email: email,
created: new Date()
};
```
可以指定這個對象的某個屬性來建立索引。
```javascript
var store = db.createObjectStore('people', { autoIncrement: true });
store.createIndex('name', 'name', { unique: false });
store.createIndex('email', 'email', { unique: true });
```
上面代碼告訴索引對象,`name`屬性不是唯一值,`email`屬性是唯一值。
**(12)IDBObjectStore.deleteIndex()**
`IDBObjectStore.deleteIndex()`方法用于刪除指定的索引。該方法只能在`VersionChange`監聽函數里面調用。
```javascript
objectStore.deleteIndex(indexName)
```
**(13)IDBObjectStore.openCursor()**
`IDBObjectStore.openCursor()`用于獲取一個指針對象。
```javascript
IDBObjectStore.openCursor()
```
指針對象可以用來遍歷數據。該對象也是異步的,有自己的`success`和`error`事件,可以對它們指定監聽函數。
```javascript
var t = db.transaction(['test'], 'readonly');
var store = t.objectStore('test');
var cursor = store.openCursor();
cursor.onsuccess = function (event) {
var res = event.target.result;
if (res) {
console.log('Key', res.key);
console.dir('Data', res.value);
res.continue();
}
}
```
監聽函數接受一個事件對象作為參數,該對象的`target.result`屬性指向當前數據記錄。該記錄的`key`和`value`分別返回主鍵和鍵值(即實際存入的數據)。`continue()`方法將光標移到下一個數據對象,如果當前數據對象已經是最后一個數據了,則光標指向`null`。
`openCursor()`方法的第一個參數是主鍵值,或者一個 IDBKeyRange 對象。如果指定該參數,將只處理包含指定主鍵的記錄;如果省略,將處理所有的記錄。該方法還可以接受第二個參數,表示遍歷方向,默認值為`next`,其他可能的值為`prev`、`nextunique`和`prevunique`。后兩個值表示如果遇到重復值,會自動跳過。
**(14)IDBObjectStore.openKeyCursor()**
`IDBObjectStore.openKeyCursor()`用于獲取一個主鍵指針對象。
```javascript
IDBObjectStore.openKeyCursor()
```
## IDBTransaction 對象
IDBTransaction 對象用來異步操作數據庫事務,所有的讀寫操作都要通過這個對象進行。
`IDBDatabase.transaction()`方法返回的就是一個 IDBTransaction 對象。
```javascript
var db;
var DBOpenRequest = window.indexedDB.open('demo', 1);
DBOpenRequest.onsuccess = function(event) {
db = DBOpenRequest.result;
var transaction = db.transaction(['demo'], 'readwrite');
transaction.oncomplete = function (event) {
console.log('transaction success');
};
transaction.onerror = function (event) {
console.log('transaction error: ' + transaction.error);
};
var objectStore = transaction.objectStore('demo');
var objectStoreRequest = objectStore.add({ foo: 1 });
objectStoreRequest.onsuccess = function (event) {
console.log('add data success');
};
};
```
事務的執行順序是按照創建的順序,而不是發出請求的順序。
```javascript
var trans1 = db.transaction('foo', 'readwrite');
var trans2 = db.transaction('foo', 'readwrite');
var objectStore2 = trans2.objectStore('foo')
var objectStore1 = trans1.objectStore('foo')
objectStore2.put('2', 'key');
objectStore1.put('1', 'key');
```
上面代碼中,`key`對應的鍵值最終是`2`,而不是`1`。因為事務`trans1`先于`trans2`創建,所以首先執行。
注意,事務有可能失敗,只有監聽到事務的`complete`事件,才能保證事務操作成功。
IDBTransaction 對象有以下屬性。
- `IDBTransaction.db`:返回當前事務所在的數據庫對象 IDBDatabase。
- `IDBTransaction.error`:返回當前事務的錯誤。如果事務沒有結束,或者事務成功結束,或者被手動終止,該方法返回`null`。
- `IDBTransaction.mode`:返回當前事務的模式,默認是`readonly`(只讀),另一個值是`readwrite`。
- `IDBTransaction.objectStoreNames`:返回一個類似數組的對象 DOMStringList,成員是當前事務涉及的對象倉庫的名字。
- `IDBTransaction.onabort`:指定`abort`事件(事務中斷)的監聽函數。
- `IDBTransaction.oncomplete`:指定`complete`事件(事務成功)的監聽函數。
- `IDBTransaction.onerror`:指定`error`事件(事務失敗)的監聽函數。
IDBTransaction 對象有以下方法。
- `IDBTransaction.abort()`:終止當前事務,回滾所有已經進行的變更。
- `IDBTransaction.objectStore(name)`:返回指定名稱的對象倉庫 IDBObjectStore。
## IDBIndex 對象
IDBIndex 對象代表數據庫的索引,通過這個對象可以獲取數據庫里面的記錄。數據記錄的主鍵默認就是帶有索引,IDBIndex 對象主要用于通過除主鍵以外的其他鍵,建立索引獲取對象。
IDBIndex 是持久性的鍵值對存儲。只要插入、更新或刪除數據記錄,引用的對象庫中的記錄,索引就會自動更新。
`IDBObjectStore.index()`方法可以獲取 IDBIndex 對象。
```javascript
var transaction = db.transaction(['contactsList'], 'readonly');
var objectStore = transaction.objectStore('contactsList');
var myIndex = objectStore.index('lName');
myIndex.openCursor().onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
var tableRow = document.createElement('tr');
tableRow.innerHTML = '<td>' + cursor.value.id + '</td>'
+ '<td>' + cursor.value.lName + '</td>'
+ '<td>' + cursor.value.fName + '</td>'
+ '<td>' + cursor.value.jTitle + '</td>'
+ '<td>' + cursor.value.company + '</td>'
+ '<td>' + cursor.value.eMail + '</td>'
+ '<td>' + cursor.value.phone + '</td>'
+ '<td>' + cursor.value.age + '</td>';
tableEntry.appendChild(tableRow);
cursor.continue();
} else {
console.log('Entries all displayed.');
}
};
```
IDBIndex 對象有以下屬性。
- `IDBIndex.name`:字符串,索引的名稱。
- `IDBIndex.objectStore`:索引所在的對象倉庫。
- `IDBIndex.keyPath`:索引的主鍵。
- `IDBIndex.multiEntry`:布爾值,針對`keyPath`為數組的情況,如果設為`true`,創建數組時,每個數組成員都會有一個條目,否則每個數組都只有一個條目。
- `IDBIndex.unique`:布爾值,表示創建索引時是否允許相同的主鍵。
IDBIndex 對象有以下方法,它們都是異步的,立即返回的都是一個 IDBRequest 對象。
- `IDBIndex.count()`:用來獲取記錄的數量。它可以接受主鍵或 IDBKeyRange 對象作為參數,這時只返回符合主鍵的記錄數量,否則返回所有記錄的數量。
- `IDBIndex.get(key)`:用來獲取符合指定主鍵的數據記錄。
- `IDBIndex.getKey(key)`:用來獲取指定的主鍵。
- `IDBIndex.getAll()`:用來獲取所有的數據記錄。它可以接受兩個參數,都是可選的,第一個參數用來指定主鍵,第二個參數用來指定返回記錄的數量。如果省略這兩個參數,則返回所有記錄。由于獲取成功時,瀏覽器必須生成所有對象,所以對性能有影響。如果數據集比較大,建議使用 IDBCursor 對象。
- `IDBIndex.getAllKeys()`:該方法與`IDBIndex.getAll()`方法相似,區別是獲取所有主鍵。
- `IDBIndex.openCursor()`:用來獲取一個 IDBCursor 對象,用來遍歷索引里面的所有條目。
- `IDBIndex.openKeyCursor()`:該方法與`IDBIndex.openCursor()`方法相似,區別是遍歷所有條目的主鍵。
## IDBCursor 對象
IDBCursor 對象代表指針對象,用來遍歷數據倉庫(IDBObjectStore)或索引(IDBIndex)的記錄。
IDBCursor 對象一般通過`IDBObjectStore.openCursor()`方法獲得。
```javascript
var transaction = db.transaction(['rushAlbumList'], 'readonly');
var objectStore = transaction.objectStore('rushAlbumList');
objectStore.openCursor(null, 'next').onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
var listItem = document.createElement('li');
listItem.innerHTML = cursor.value.albumTitle + ', ' + cursor.value.year;
list.appendChild(listItem);
console.log(cursor.source);
cursor.continue();
} else {
console.log('Entries all displayed.');
}
};
};
```
IDBCursor 對象的屬性。
- `IDBCursor.source`:返回正在遍歷的對象倉庫或索引。
- `IDBCursor.direction`:字符串,表示指針遍歷的方向。共有四個可能的值:next(從頭開始向后遍歷)、nextunique(從頭開始向后遍歷,重復的值只遍歷一次)、prev(從尾部開始向前遍歷)、prevunique(從尾部開始向前遍歷,重復的值只遍歷一次)。該屬性通過`IDBObjectStore.openCursor()`方法的第二個參數指定,一旦指定就不能改變了。
- `IDBCursor.key`:返回當前記錄的主鍵。
- `IDBCursor.value`:返回當前記錄的數據值。
- `IDBCursor.primaryKey`:返回當前記錄的主鍵。對于數據倉庫(objectStore)來說,這個屬性等同于 IDBCursor.key;對于索引,IDBCursor.key 返回索引的位置值,該屬性返回數據記錄的主鍵。
IDBCursor 對象有如下方法。
- `IDBCursor.advance(n)`:指針向前移動 n 個位置。
- `IDBCursor.continue()`:指針向前移動一個位置。它可以接受一個主鍵作為參數,這時會跳轉到這個主鍵。
- `IDBCursor.continuePrimaryKey()`:該方法需要兩個參數,第一個是`key`,第二個是`primaryKey`,將指針移到符合這兩個參數的位置。
- `IDBCursor.delete()`:用來刪除當前位置的記錄,返回一個 IDBRequest 對象。該方法不會改變指針的位置。
- `IDBCursor.update()`:用來更新當前位置的記錄,返回一個 IDBRequest 對象。它的參數是要寫入數據庫的新的值。
## IDBKeyRange 對象
IDBKeyRange 對象代表數據倉庫(object store)里面的一組主鍵。根據這組主鍵,可以獲取數據倉庫或索引里面的一組記錄。
IDBKeyRange 可以只包含一個值,也可以指定上限和下限。它有四個靜態方法,用來指定主鍵的范圍。
- `IDBKeyRange.lowerBound()`:指定下限。
- `IDBKeyRange.upperBound()`:指定上限。
- `IDBKeyRange.bound()`:同時指定上下限。
- `IDBKeyRange.only()`:指定只包含一個值。
下面是一些代碼實例。
```javascript
// All keys ≤ x
var r1 = IDBKeyRange.upperBound(x);
// All keys < x
var r2 = IDBKeyRange.upperBound(x, true);
// All keys ≥ y
var r3 = IDBKeyRange.lowerBound(y);
// All keys > y
var r4 = IDBKeyRange.lowerBound(y, true);
// All keys ≥ x && ≤ y
var r5 = IDBKeyRange.bound(x, y);
// All keys > x &&< y
var r6 = IDBKeyRange.bound(x, y, true, true);
// All keys > x && ≤ y
var r7 = IDBKeyRange.bound(x, y, true, false);
// All keys ≥ x &&< y
var r8 = IDBKeyRange.bound(x, y, false, true);
// The key = z
var r9 = IDBKeyRange.only(z);
```
`IDBKeyRange.lowerBound()`、`IDBKeyRange.upperBound()`、`IDBKeyRange.bound()`這三個方法默認包括端點值,可以傳入一個布爾值,修改這個屬性。
與之對應,IDBKeyRange 對象有四個只讀屬性。
- `IDBKeyRange.lower`:返回下限
- `IDBKeyRange.lowerOpen`:布爾值,表示下限是否為開區間(即下限是否排除在范圍之外)
- `IDBKeyRange.upper`:返回上限
- `IDBKeyRange.upperOpen`:布爾值,表示上限是否為開區間(即上限是否排除在范圍之外)
IDBKeyRange 實例對象生成以后,將它作為參數輸入 IDBObjectStore 或 IDBIndex 對象的`openCursor()`方法,就可以在所設定的范圍內讀取數據。
```javascript
var t = db.transaction(['people'], 'readonly');
var store = t.objectStore('people');
var index = store.index('name');
var range = IDBKeyRange.bound('B', 'D');
index.openCursor(range).onsuccess = function (e) {
var cursor = e.target.result;
if (cursor) {
console.log(cursor.key + ':');
for (var field in cursor.value) {
console.log(cursor.value[field]);
}
cursor.continue();
}
}
```
IDBKeyRange 有一個實例方法`includes(key)`,返回一個布爾值,表示某個主鍵是否包含在當前這個主鍵組之內。
```javascript
var keyRangeValue = IDBKeyRange.bound('A', 'K', false, false);
keyRangeValue.includes('F') // true
keyRangeValue.includes('W') // false
```
## 參考鏈接
- Raymond Camden, [Working With IndexedDB – Part 1](http://net.tutsplus.com/tutorials/javascript-ajax/working-with-indexeddb/)
- Raymond Camden, [Working With IndexedDB – Part 2](http://net.tutsplus.com/tutorials/javascript-ajax/working-with-indexeddb-part-2/)
- Raymond Camden, [Working With IndexedDB - Part 3](https://code.tutsplus.com/tutorials/working-with-indexeddb-part-3--net-36220)
- Tiffany Brown, [An Introduction to IndexedDB](http://dev.opera.com/articles/introduction-to-indexeddb/)
- David Fahlander, [Breaking the Borders of IndexedDB](https://hacks.mozilla.org/2014/06/breaking-the-borders-of-indexeddb/)
- TutorialsPoint, [HTML5 - IndexedDB](https://www.tutorialspoint.com/html5/html5_indexeddb.htm)
- 前言
- 入門篇
- 導論
- 歷史
- 基本語法
- 數據類型
- 概述
- null,undefined 和布爾值
- 數值
- 字符串
- 對象
- 函數
- 數組
- 運算符
- 算術運算符
- 比較運算符
- 布爾運算符
- 二進制位運算符
- 其他運算符,運算順序
- 語法專題
- 數據類型的轉換
- 錯誤處理機制
- 編程風格
- console 對象與控制臺
- 標準庫
- Object 對象
- 屬性描述對象
- Array 對象
- 包裝對象
- Boolean 對象
- Number 對象
- String 對象
- Math 對象
- Date 對象
- RegExp 對象
- JSON 對象
- 面向對象編程
- 實例對象與 new 命令
- this 關鍵字
- 對象的繼承
- Object 對象的相關方法
- 嚴格模式
- 異步操作
- 概述
- 定時器
- Promise 對象
- DOM
- 概述
- Node 接口
- NodeList 接口,HTMLCollection 接口
- ParentNode 接口,ChildNode 接口
- Document 節點
- Element 節點
- 屬性的操作
- Text 節點和 DocumentFragment 節點
- CSS 操作
- Mutation Observer API
- 事件
- EventTarget 接口
- 事件模型
- Event 對象
- 鼠標事件
- 鍵盤事件
- 進度事件
- 表單事件
- 觸摸事件
- 拖拉事件
- 其他常見事件
- GlobalEventHandlers 接口
- 瀏覽器模型
- 瀏覽器模型概述
- window 對象
- Navigator 對象,Screen 對象
- Cookie
- XMLHttpRequest 對象
- 同源限制
- CORS 通信
- Storage 接口
- History 對象
- Location 對象,URL 對象,URLSearchParams 對象
- ArrayBuffer 對象,Blob 對象
- File 對象,FileList 對象,FileReader 對象
- 表單,FormData 對象
- IndexedDB API
- Web Worker
- 附錄:網頁元素接口
- a
- img
- form
- input
- button
- option
- video,audio