隨著瀏覽器的功能不斷增強(qiáng),越來越多的網(wǎng)站開始考慮,將大量數(shù)據(jù)儲存在客戶端,這樣可以減少從服務(wù)器獲取數(shù)據(jù),直接從本地獲取數(shù)據(jù)。
現(xiàn)有的瀏覽器數(shù)據(jù)儲存方案,都不適合儲存大量數(shù)據(jù):Cookie 的大小不超過 4KB,且每次請求都會發(fā)送回服務(wù)器;LocalStorage 在 2.5MB 到 10MB 之間(各家瀏覽器不同),而且不提供搜索功能,不能建立自定義的索引。所以,需要一種新的解決方案,這就是 IndexedDB 誕生的背景。
通俗地說,IndexedDB 就是瀏覽器提供的本地?cái)?shù)據(jù)庫,它可以被網(wǎng)頁腳本創(chuàng)建和操作。IndexedDB 允許儲存大量數(shù)據(jù),提供查找接口,還能建立索引。這些都是 LocalStorage 所不具備的。就數(shù)據(jù)庫類型而言,IndexedDB 不屬于關(guān)系型數(shù)據(jù)庫(不支持 SQL 查詢語句),更接近 NoSQL 數(shù)據(jù)庫。
IndexedDB 具有以下特點(diǎn)。
(1)鍵值對儲存。 IndexedDB 內(nèi)部采用對象倉庫(object store)存放數(shù)據(jù)。所有類型的數(shù)據(jù)都可以直接存入,包括 JavaScript 對象。對象倉庫中,數(shù)據(jù)以“鍵值對”的形式保存,每一個數(shù)據(jù)記錄都有對應(yīng)的主鍵,主鍵是獨(dú)一無二的,不能有重復(fù),否則會拋出一個錯誤。
(2)異步。 IndexedDB 操作時不會鎖死瀏覽器,用戶依然可以進(jìn)行其他操作,這與 LocalStorage 形成對比,后者的操作是同步的。異步設(shè)計(jì)是為了防止大量數(shù)據(jù)的讀寫,拖慢網(wǎng)頁的表現(xiàn)。
(3)支持事務(wù)。 IndexedDB 支持事務(wù)(transaction),這意味著一系列操作步驟之中,只要有一步失敗,整個事務(wù)就都取消,數(shù)據(jù)庫回滾到事務(wù)發(fā)生之前的狀態(tài),不存在只改寫一部分?jǐn)?shù)據(jù)的情況。
(4)同源限制。 IndexedDB 受到同源限制,每一個數(shù)據(jù)庫對應(yīng)創(chuàng)建它的域名。網(wǎng)頁只能訪問自身域名下的數(shù)據(jù)庫,而不能訪問跨域的數(shù)據(jù)庫。
(5)儲存空間大。 IndexedDB 的儲存空間比 LocalStorage 大得多,一般來說不少于 250MB,甚至沒有上限。
(6)支持二進(jìn)制儲存。 IndexedDB 不僅可以儲存字符串,還可以儲存二進(jìn)制數(shù)據(jù)(ArrayBuffer 對象和 Blob 對象)。
基本概念
IndexedDB 是一個比較復(fù)雜的 API,涉及不少概念。它把不同的實(shí)體,抽象成一個個對象接口。學(xué)習(xí)這個 API,就是學(xué)習(xí)它的各種對象接口。
- 數(shù)據(jù)庫:IDBDatabase 對象
- 對象倉庫:IDBObjectStore 對象
- 索引: IDBIndex 對象
- 事務(wù): IDBTransaction 對象
- 操作請求:IDBRequest 對象
- 指針: IDBCursor 對象
- 主鍵集合:IDBKeyRange 對象
下面是一些主要的概念。
(1)數(shù)據(jù)庫
數(shù)據(jù)庫是一系列相關(guān)數(shù)據(jù)的容器。每個域名(嚴(yán)格的說,是協(xié)議 + 域名 + 端口)都可以新建任意多個數(shù)據(jù)庫。
IndexedDB 數(shù)據(jù)庫有版本的概念。同一個時刻,只能有一個版本的數(shù)據(jù)庫存在。如果要修改數(shù)據(jù)庫結(jié)構(gòu)(新增或刪除表、索引或者主鍵),只能通過升級數(shù)據(jù)庫版本完成。
(2)對象倉庫
每個數(shù)據(jù)庫包含若干個對象倉庫(object store)。它類似于關(guān)系型數(shù)據(jù)庫的表格。
(3)數(shù)據(jù)記錄
對象倉庫保存的是數(shù)據(jù)記錄。每條記錄類似于關(guān)系型數(shù)據(jù)庫的行,但是只有主鍵和數(shù)據(jù)體兩部分。主鍵用來建立默認(rèn)的索引,必須是不同的,否則會報(bào)錯。主鍵可以是數(shù)據(jù)記錄里面的一個屬性,也可以指定為一個遞增的整數(shù)編號。
{ id: 1, text: 'foo' }
上面的對象中,id
屬性可以當(dāng)作主鍵。
數(shù)據(jù)體可以是任意數(shù)據(jù)類型,不限于對象。
(4)索引
為了加速數(shù)據(jù)的檢索,可以在對象倉庫里面,為不同的屬性建立索引。
(5)事務(wù)
數(shù)據(jù)記錄的讀寫和刪改,都要通過事務(wù)完成。事務(wù)對象提供error
、abort
和complete
三個事件,用來監(jiān)聽操作結(jié)果。
操作流程
IndexedDB 數(shù)據(jù)庫的各種操作,一般是按照下面的流程進(jìn)行的。這個部分只給出簡單的代碼示例,用于快速上手,詳細(xì)的各個對象的 API 放在后文介紹。
打開數(shù)據(jù)庫
使用 IndexedDB 的第一步是打開數(shù)據(jù)庫,使用indexedDB.open()
方法。
var request = window.indexedDB.open(databaseName, version);
這個方法接受兩個參數(shù),第一個參數(shù)是字符串,表示數(shù)據(jù)庫的名字。如果指定的數(shù)據(jù)庫不存在,就會新建數(shù)據(jù)庫。第二個參數(shù)是整數(shù),表示數(shù)據(jù)庫的版本。如果省略,打開已有數(shù)據(jù)庫時,默認(rèn)為當(dāng)前版本;新建數(shù)據(jù)庫時,默認(rèn)為1
。
indexedDB.open()
方法返回一個 IDBRequest 對象。這個對象通過三種事件error
、success
、upgradeneeded
,處理打開數(shù)據(jù)庫的操作結(jié)果。
(1)error 事件
error
事件表示打開數(shù)據(jù)庫失敗。
request.onerror = function (event) {
console.log('數(shù)據(jù)庫打開報(bào)錯');
};
(2)success 事件
success
事件表示成功打開數(shù)據(jù)庫。
var db;
request.onsuccess = function (event) {
db = request.result;
console.log('數(shù)據(jù)庫打開成功');
};
這時,通過request
對象的result
屬性拿到數(shù)據(jù)庫對象。
(3)upgradeneeded 事件
如果指定的版本號,大于數(shù)據(jù)庫的實(shí)際版本號,就會發(fā)生數(shù)據(jù)庫升級事件upgradeneeded
。
var db;
request.onupgradeneeded = function (event) {
db = event.target.result;
}
這時通過事件對象的target.result
屬性,拿到數(shù)據(jù)庫實(shí)例。
新建數(shù)據(jù)庫
新建數(shù)據(jù)庫與打開數(shù)據(jù)庫是同一個操作。如果指定的數(shù)據(jù)庫不存在,就會新建。不同之處在于,后續(xù)的操作主要在upgradeneeded
事件的監(jiān)聽函數(shù)里面完成,因?yàn)檫@時版本從無到有,所以會觸發(fā)這個事件。
通常,新建數(shù)據(jù)庫以后,第一件事是新建對象倉庫(即新建表)。
request.onupgradeneeded = function(event) {
db = event.target.result;
var objectStore = db.createObjectStore('person', { keyPath: 'id' });
}
上面代碼中,數(shù)據(jù)庫新建成功以后,新增一張叫做person
的表格,主鍵是id
。
更好的寫法是先判斷一下,這張表格是否存在,如果不存在再新建。
request.onupgradeneeded = function (event) {
db = event.target.result;
var objectStore;
if (!db.objectStoreNames.contains('person')) {
objectStore = db.createObjectStore('person', { keyPath: 'id' });
}
}
主鍵(key)是默認(rèn)建立索引的屬性。比如,數(shù)據(jù)記錄是{ id: 1, name: '張三' }
,那么id
屬性可以作為主鍵。主鍵也可以指定為下一層對象的屬性,比如{ foo: { bar: 'baz' } }
的foo.bar
也可以指定為主鍵。
如果數(shù)據(jù)記錄里面沒有合適作為主鍵的屬性,那么可以讓 IndexedDB 自動生成主鍵。
var objectStore = db.createObjectStore(
'person',
{ autoIncrement: true }
);
上面代碼中,指定主鍵為一個遞增的整數(shù)。
新建對象倉庫以后,下一步可以新建索引。
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()
的三個參數(shù)分別為索引名稱、索引所在的屬性、配置對象(說明該屬性是否包含重復(fù)的值)。
新增數(shù)據(jù)
新增數(shù)據(jù)指的是向?qū)ο髠}庫寫入數(shù)據(jù)記錄。這需要通過事務(wù)完成。
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('數(shù)據(jù)寫入成功');
};
request.onerror = function (event) {
console.log('數(shù)據(jù)寫入失敗');
}
}
add();
上面代碼中,寫入數(shù)據(jù)需要新建一個事務(wù)。新建時必須指定表格名稱和操作模式(“只讀”或“讀寫”)。新建事務(wù)以后,通過IDBTransaction.objectStore(name)
方法,拿到 IDBObjectStore 對象,再通過表格對象的add()
方法,向表格寫入一條記錄。
寫入操作是一個異步操作,通過監(jiān)聽連接對象的success
事件和error
事件,了解是否寫入成功。
讀取數(shù)據(jù)
讀取數(shù)據(jù)也是通過事務(wù)完成。
function read() {
var transaction = db.transaction(['person']);
var objectStore = transaction.objectStore('person');
var request = objectStore.get(1);
request.onerror = function(event) {
console.log('事務(wù)失敗');
};
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('未獲得數(shù)據(jù)記錄');
}
};
}
read();
上面代碼中,objectStore.get()
方法用于讀取數(shù)據(jù),參數(shù)是主鍵的值。
遍歷數(shù)據(jù)
遍歷數(shù)據(jù)表格的所有記錄,要使用指針對象 IDBCursor。
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('沒有更多數(shù)據(jù)了!');
}
};
}
readAll();
上面代碼中,新建指針對象的openCursor()
方法是一個異步操作,所以要監(jiān)聽success
事件。
更新數(shù)據(jù)
更新數(shù)據(jù)要使用IDBObject.put()
方法。
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('數(shù)據(jù)更新成功');
};
request.onerror = function (event) {
console.log('數(shù)據(jù)更新失敗');
}
}
update();
上面代碼中,put()
方法自動更新了主鍵為1
的記錄。
刪除數(shù)據(jù)
IDBObjectStore.delete()
方法用于刪除記錄。
function remove() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.delete(1);
request.onsuccess = function (event) {
console.log('數(shù)據(jù)刪除成功');
};
}
remove();
使用索引
索引的意義在于,可以讓你搜索任意字段,也就是說從任意字段拿到數(shù)據(jù)記錄。如果不建立索引,默認(rèn)只能搜索主鍵(即從主鍵取值)。
假定新建表格的時候,對name
字段建立了索引。
objectStore.createIndex('name', 'name', { unique: false });
現(xiàn)在,就可以從name
找到對應(yīng)的數(shù)據(jù)記錄了。
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
對象,作為開發(fā)者的操作接口。
indexedDB.open()
indexedDB.open()
方法用于打開數(shù)據(jù)庫。這是一個異步操作,但是會立刻返回一個 IDBOpenDBRequest 對象。
var openRequest = window.indexedDB.open('test', 1);
上面代碼表示,打開一個名為test
、版本為1
的數(shù)據(jù)庫。如果該數(shù)據(jù)庫不存在,則會新建該數(shù)據(jù)庫。
open()
方法的第一個參數(shù)是數(shù)據(jù)庫名稱,格式為字符串,不可省略;第二個參數(shù)是數(shù)據(jù)庫版本,是一個大于0
的正整數(shù)(0
將報(bào)錯),如果該參數(shù)大于當(dāng)前版本,會觸發(fā)數(shù)據(jù)庫升級。第二個參數(shù)可省略,如果數(shù)據(jù)庫已存在,將打開當(dāng)前版本的數(shù)據(jù)庫;如果數(shù)據(jù)庫不存在,將創(chuàng)建該版本的數(shù)據(jù)庫,默認(rèn)版本為1
。
打開數(shù)據(jù)庫是異步操作,通過各種事件通知客戶端。下面是有可能觸發(fā)的4種事件。
- success:打開成功。
- error:打開失敗。
- upgradeneeded:第一次打開該數(shù)據(jù)庫,或者數(shù)據(jù)庫版本發(fā)生變化。
- blocked:上一次的數(shù)據(jù)庫連接還未關(guān)閉。
第一次打開數(shù)據(jù)庫時,會先觸發(fā)upgradeneeded
事件,然后觸發(fā)success
事件。
根據(jù)不同的需要,對上面4種事件監(jiān)聽函數(shù)。
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),監(jiān)聽函數(shù)就定義在這個對象上面。其次,success
事件發(fā)生后,從openRequest.result
屬性可以拿到已經(jīng)打開的IndexedDB
數(shù)據(jù)庫對象。
indexedDB.deleteDatabase()
indexedDB.deleteDatabase()
方法用于刪除一個數(shù)據(jù)庫,參數(shù)為數(shù)據(jù)庫的名字。它會立刻返回一個IDBOpenDBRequest
對象,然后對數(shù)據(jù)庫執(zhí)行異步刪除。刪除操作的結(jié)果會通過事件通知,IDBOpenDBRequest
對象可以監(jiān)聽以下事件。
success
:刪除成功error
:刪除報(bào)錯
var DBDeleteRequest = window.indexedDB.deleteDatabase('demo');
DBDeleteRequest.onerror = function (event) {
console.log('Error');
};
DBDeleteRequest.onsuccess = function (event) {
console.log('success');
};
調(diào)用deleteDatabase()
方法以后,當(dāng)前數(shù)據(jù)庫的其他已經(jīng)打開的連接都會接收到versionchange
事件。
注意,刪除不存在的數(shù)據(jù)庫并不會報(bào)錯。
indexedDB.cmp()
indexedDB.cmp()
方法比較兩個值是否為 indexedDB 的相同的主鍵。它返回一個整數(shù),表示比較的結(jié)果:0
表示相同,1
表示第一個主鍵大于第二個主鍵,-1
表示第一個主鍵小于第二個主鍵。
window.indexedDB.cmp(1, 2) // -1
注意,這個方法不能用來比較任意的 JavaScript 值。如果參數(shù)是布爾值或?qū)ο?,它會?bào)錯。
window.indexedDB.cmp(1, true) // 報(bào)錯
window.indexedDB.cmp({}, {}) // 報(bào)錯
IDBRequest 對象
IDBRequest 對象表示打開的數(shù)據(jù)庫連接,indexedDB.open()
方法和indexedDB.deleteDatabase()
方法會返回這個對象。數(shù)據(jù)庫的操作都是通過這個對象完成的。
這個對象的所有操作都是異步操作,要通過readyState
屬性判斷是否完成,如果為pending
就表示操作正在進(jìn)行,如果為done
就表示操作完成,可能成功也可能失敗。
操作完成以后,觸發(fā)success
事件或error
事件,這時可以通過result
屬性和error
屬性拿到操作結(jié)果。如果在pending
階段,就去讀取這兩個屬性,是會報(bào)錯的。
IDBRequest 對象有以下屬性。
IDBRequest.readyState
:等于pending
表示操作正在進(jìn)行,等于done
表示操作正在完成。IDBRequest.result
:返回請求的結(jié)果。如果請求失敗、結(jié)果不可用,讀取該屬性會報(bào)錯。IDBRequest.error
:請求失敗時,返回錯誤對象。IDBRequest.source
:返回請求的來源(比如索引對象或 ObjectStore)。IDBRequest.transaction
:返回當(dāng)前請求正在進(jìn)行的事務(wù),如果不包含事務(wù),返回null
。IDBRequest.onsuccess
:指定success
事件的監(jiān)聽函數(shù)。IDBRequest.onerror
:指定error
事件的監(jiān)聽函數(shù)。
IDBOpenDBRequest 對象繼承了 IDBRequest 對象,提供了兩個額外的事件監(jiān)聽屬性。
IDBOpenDBRequest.onblocked
:指定blocked
事件(upgradeneeded
事件觸發(fā)時,數(shù)據(jù)庫仍然在使用)的監(jiān)聽函數(shù)。IDBOpenDBRequest.onupgradeneeded
:upgradeneeded
事件的監(jiān)聽函數(shù)。
IDBDatabase 對象
打開數(shù)據(jù)成功以后,可以從IDBOpenDBRequest
對象的result
屬性上面,拿到一個IDBDatabase
對象,它表示連接的數(shù)據(jù)庫。后面對數(shù)據(jù)庫的操作,都通過這個對象完成。
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
:字符串,數(shù)據(jù)庫名稱。IDBDatabase.version
:整數(shù),數(shù)據(jù)庫版本。數(shù)據(jù)庫第一次創(chuàng)建時,該屬性為空字符串。IDBDatabase.objectStoreNames
:DOMStringList 對象(字符串的集合),包含當(dāng)前數(shù)據(jù)的所有 object store 的名字。IDBDatabase.onabort
:指定 abort 事件(事務(wù)中止)的監(jiān)聽函數(shù)。IDBDatabase.onclose
:指定 close 事件(數(shù)據(jù)庫意外關(guān)閉)的監(jiān)聽函數(shù)。IDBDatabase.onerror
:指定 error 事件(訪問數(shù)據(jù)庫失?。┑谋O(jiān)聽函數(shù)。IDBDatabase.onversionchange
:數(shù)據(jù)庫版本變化時觸發(fā)(發(fā)生upgradeneeded
事件,或調(diào)用indexedDB.deleteDatabase()
)。
下面是objectStoreNames
屬性的例子。該屬性返回一個 DOMStringList 對象,包含了當(dāng)前數(shù)據(jù)庫所有對象倉庫的名稱(即表名),可以使用 DOMStringList 對象的contains
方法,檢查數(shù)據(jù)庫是否包含某個對象倉庫。
if (!db.objectStoreNames.contains('firstOS')) {
db.createObjectStore('firstOS');
}
上面代碼先判斷某個對象倉庫是否存在,如果不存在就創(chuàng)建該對象倉庫。
方法
IDBDatabase 對象有以下方法。
IDBDatabase.close()
:關(guān)閉數(shù)據(jù)庫連接,實(shí)際會等所有事務(wù)完成后再關(guān)閉。IDBDatabase.createObjectStore()
:創(chuàng)建存放數(shù)據(jù)的對象倉庫,類似于傳統(tǒng)關(guān)系型數(shù)據(jù)庫的表格,返回一個 IDBObjectStore 對象。該方法只能在versionchange
事件監(jiān)聽函數(shù)中調(diào)用。IDBDatabase.deleteObjectStore()
:刪除指定的對象倉庫。該方法只能在versionchange
事件監(jiān)聽函數(shù)中調(diào)用。IDBDatabase.transaction()
:返回一個 IDBTransaction 事務(wù)對象。
下面是createObjectStore()
方法的例子。
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');
// ...
};
上面代碼創(chuàng)建了一個名為items
的對象倉庫,如果該對象倉庫已經(jīng)存在,就會拋出一個錯誤。為了避免出錯,需要用到下文的objectStoreNames
屬性,檢查已有哪些對象倉庫。
createObjectStore()
方法還可以接受第二個對象參數(shù),用來設(shè)置對象倉庫的屬性。
db.createObjectStore('test', { keyPath: 'email' });
db.createObjectStore('test2', { autoIncrement: true });
上面代碼中,keyPath
屬性表示主鍵(由于主鍵的值不能重復(fù),所以上例存入之前,必須保證數(shù)據(jù)的email
屬性值都是不一樣的),默認(rèn)值為null
;autoIncrement
屬性表示,是否使用自動遞增的整數(shù)作為主鍵(第一個數(shù)據(jù)記錄為1,第二個數(shù)據(jù)記錄為2,以此類推),默認(rèn)為false
。一般來說,keyPath
和autoIncrement
屬性只要使用一個就夠了,如果兩個同時使用,表示主鍵為遞增的整數(shù),且對象不得缺少keyPath
指定的屬性。
下面是deleteObjectStore()
方法的例子。
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()
方法的例子,該方法用于創(chuàng)建一個數(shù)據(jù)庫事務(wù),返回一個 IDBTransaction 對象。向數(shù)據(jù)庫添加數(shù)據(jù)之前,必須先創(chuàng)建數(shù)據(jù)庫事務(wù)。
var t = db.transaction(['items'], 'readwrite');
transaction()
方法接受兩個參數(shù):第一個參數(shù)是一個數(shù)組,里面是所涉及的對象倉庫,通常是只有一個;第二個參數(shù)是一個表示操作類型的字符串。目前,操作類型只有兩種:readonly
(只讀)和readwrite
(讀寫)。添加數(shù)據(jù)使用readwrite
,讀取數(shù)據(jù)使用readonly
。第二個參數(shù)是可選的,省略時默認(rèn)為readonly
模式。
IDBObjectStore 對象
IDBObjectStore 對象對應(yīng)一個對象倉庫(object store)。IDBDatabase.createObjectStore()
方法返回的就是一個 IDBObjectStore 對象。
IDBDatabase 對象的transaction()
返回一個事務(wù)對象,該對象的objectStore()
方法返回 IDBObjectStore 對象,因此可以采用下面的鏈?zhǔn)綄懛ā?/p>
db.transaction(['test'], 'readonly')
.objectStore('test')
.get(X)
.onsuccess = function (e) {}
屬性
IDBObjectStore 對象有以下屬性。
IDBObjectStore.indexNames
:返回一個類似數(shù)組的對象(DOMStringList),包含了當(dāng)前對象倉庫的所有索引。IDBObjectStore.keyPath
:返回當(dāng)前對象倉庫的主鍵。IDBObjectStore.name
:返回當(dāng)前對象倉庫的名稱。IDBObjectStore.transaction
:返回當(dāng)前對象倉庫所屬的事務(wù)對象。IDBObjectStore.autoIncrement
:布爾值,表示主鍵是否會自動遞增。
方法
IDBObjectStore 對象有以下方法。
(1)IDBObjectStore.add()
IDBObjectStore.add()
用于向?qū)ο髠}庫添加數(shù)據(jù),返回一個 IDBRequest 對象。該方法只用于添加數(shù)據(jù),如果主鍵相同會報(bào)錯,因此更新數(shù)據(jù)必須使用put()
方法。
objectStore.add(value, key)
該方法接受兩個參數(shù),第一個參數(shù)是鍵值,第二個參數(shù)是主鍵,該參數(shù)可選,如果省略默認(rèn)為null
。
創(chuàng)建事務(wù)以后,就可以獲取對象倉庫,然后使用add()
方法往里面添加數(shù)據(jù)了。
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()
方法用于更新某個主鍵對應(yīng)的數(shù)據(jù)記錄,如果對應(yīng)的鍵值不存在,則插入一條新的記錄。該方法返回一個 IDBRequest 對象。
objectStore.put(item, key)
該方法接受兩個參數(shù),第一個參數(shù)為新數(shù)據(jù),第二個參數(shù)為主鍵,該參數(shù)可選,且只在自動遞增時才有必要提供,因?yàn)槟菚r主鍵不包含在數(shù)據(jù)值里面。
(3)IDBObjectStore.clear()
IDBObjectStore.clear()
刪除當(dāng)前對象倉庫的所有記錄。該方法返回一個 IDBRequest 對象。
objectStore.clear()
該方法不需要參數(shù)。
(4)IDBObjectStore.delete()
IDBObjectStore.delete()
方法用于刪除指定主鍵的記錄。該方法返回一個 IDBRequest 對象。
objectStore.delete(Key)
該方法的參數(shù)為主鍵的值。
(5)IDBObjectStore.count()
IDBObjectStore.count()
方法用于計(jì)算記錄的數(shù)量。該方法返回一個 IDBRequest 對象。
IDBObjectStore.count(key)
不帶參數(shù)時,該方法返回當(dāng)前對象倉庫的所有記錄數(shù)量。如果主鍵或 IDBKeyRange 對象作為參數(shù),則返回對應(yīng)的記錄數(shù)量。
(6)IDBObjectStore.getKey()
IDBObjectStore.getKey()
用于獲取主鍵。該方法返回一個 IDBRequest 對象。
objectStore.getKey(key)
該方法的參數(shù)可以是主鍵值或 IDBKeyRange 對象。
(7)IDBObjectStore.get()
IDBObjectStore.get()
用于獲取主鍵對應(yīng)的數(shù)據(jù)記錄。該方法返回一個 IDBRequest 對象。
objectStore.get(key)
(8)IDBObjectStore.getAll()
DBObjectStore.getAll()
用于獲取對象倉庫的記錄。該方法返回一個 IDBRequest 對象。
// 獲取所有記錄
objectStore.getAll()
// 獲取所有符合指定主鍵或 IDBKeyRange 的記錄
objectStore.getAll(query)
// 指定獲取記錄的數(shù)量
objectStore.getAll(query, count)
(9)IDBObjectStore.getAllKeys()
IDBObjectStore.getAllKeys()
用于獲取所有符合條件的主鍵。該方法返回一個 IDBRequest 對象。
// 獲取所有記錄的主鍵
objectStore.getAllKeys()
// 獲取所有符合條件的主鍵
objectStore.getAllKeys(query)
// 指定獲取主鍵的數(shù)量
objectStore.getAllKeys(query, count)
(10)IDBObjectStore.index()
IDBObjectStore.index()
方法返回指定名稱的索引對象 IDBIndex。
objectStore.index(name)
有了索引以后,就可以針對索引所在的屬性讀取數(shù)據(jù)。
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
)對應(yīng)的數(shù)據(jù)。如果name
屬性不是對應(yīng)唯一值,這時get()
方法有可能取回多個數(shù)據(jù)對象。另外,get()
是異步方法,讀取成功以后,只能在success
事件的監(jiān)聽函數(shù)中處理數(shù)據(jù)。
(11)IDBObjectStore.createIndex()
IDBObjectStore.createIndex()
方法用于新建當(dāng)前數(shù)據(jù)庫的一個索引。該方法只能在VersionChange
監(jiān)聽函數(shù)里面調(diào)用。
objectStore.createIndex(indexName, keyPath, objectParameters)
該方法可以接受三個參數(shù)。
- indexName:索引名
- keyPath:主鍵
- objectParameters:配置對象(可選)
第三個參數(shù)可以配置以下屬性。
- unique:如果設(shè)為
true
,將不允許重復(fù)的值 - multiEntry:如果設(shè)為
true
,對于有多個值的主鍵數(shù)組,每個值將在索引里面新建一個條目,否則主鍵數(shù)組對應(yīng)一個條目。
假定對象倉庫中的數(shù)據(jù)記錄都是如下的person
類型。
var person = {
name: name,
email: email,
created: new Date()
};
可以指定這個對象的某個屬性來建立索引。
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
監(jiān)聽函數(shù)里面調(diào)用。
objectStore.deleteIndex(indexName)
(13)IDBObjectStore.openCursor()
IDBObjectStore.openCursor()
用于獲取一個指針對象。
IDBObjectStore.openCursor()
指針對象可以用來遍歷數(shù)據(jù)。該對象也是異步的,有自己的success
和error
事件,可以對它們指定監(jiān)聽函數(shù)。
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();
}
}
監(jiān)聽函數(shù)接受一個事件對象作為參數(shù),該對象的target.result
屬性指向當(dāng)前數(shù)據(jù)記錄。該記錄的key
和value
分別返回主鍵和鍵值(即實(shí)際存入的數(shù)據(jù))。continue()
方法將光標(biāo)移到下一個數(shù)據(jù)對象,如果當(dāng)前數(shù)據(jù)對象已經(jīng)是最后一個數(shù)據(jù)了,則光標(biāo)指向null
。
openCursor()
方法的第一個參數(shù)是主鍵值,或者一個 IDBKeyRange 對象。如果指定該參數(shù),將只處理包含指定主鍵的記錄;如果省略,將處理所有的記錄。該方法還可以接受第二個參數(shù),表示遍歷方向,默認(rèn)值為next
,其他可能的值為prev
、nextunique
和prevunique
。后兩個值表示如果遇到重復(fù)值,會自動跳過。
(14)IDBObjectStore.openKeyCursor()
IDBObjectStore.openKeyCursor()
用于獲取一個主鍵指針對象。
IDBObjectStore.openKeyCursor()
IDBTransaction 對象
IDBTransaction 對象用來異步操作數(shù)據(jù)庫事務(wù),所有的讀寫操作都要通過這個對象進(jìn)行。
IDBDatabase.transaction()
方法返回的就是一個 IDBTransaction 對象。
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');
};
};
事務(wù)的執(zhí)行順序是按照創(chuàng)建的順序,而不是發(fā)出請求的順序。
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
對應(yīng)的鍵值最終是2
,而不是1
。因?yàn)槭聞?wù)trans1
先于trans2
創(chuàng)建,所以首先執(zhí)行。
注意,事務(wù)有可能失敗,只有監(jiān)聽到事務(wù)的complete
事件,才能保證事務(wù)操作成功。
IDBTransaction 對象有以下屬性。
IDBTransaction.db
:返回當(dāng)前事務(wù)所在的數(shù)據(jù)庫對象 IDBDatabase。IDBTransaction.error
:返回當(dāng)前事務(wù)的錯誤。如果事務(wù)沒有結(jié)束,或者事務(wù)成功結(jié)束,或者被手動終止,該方法返回null
。IDBTransaction.mode
:返回當(dāng)前事務(wù)的模式,默認(rèn)是readonly
(只讀),另一個值是readwrite
。IDBTransaction.objectStoreNames
:返回一個類似數(shù)組的對象 DOMStringList,成員是當(dāng)前事務(wù)涉及的對象倉庫的名字。IDBTransaction.onabort
:指定abort
事件(事務(wù)中斷)的監(jiān)聽函數(shù)。IDBTransaction.oncomplete
:指定complete
事件(事務(wù)成功)的監(jiān)聽函數(shù)。IDBTransaction.onerror
:指定error
事件(事務(wù)失?。┑谋O(jiān)聽函數(shù)。
IDBTransaction 對象有以下方法。
IDBTransaction.abort()
:終止當(dāng)前事務(wù),回滾所有已經(jīng)進(jìn)行的變更。IDBTransaction.objectStore(name)
:返回指定名稱的對象倉庫 IDBObjectStore。
IDBIndex 對象
IDBIndex 對象代表數(shù)據(jù)庫的索引,通過這個對象可以獲取數(shù)據(jù)庫里面的記錄。數(shù)據(jù)記錄的主鍵默認(rèn)就是帶有索引,IDBIndex 對象主要用于通過除主鍵以外的其他鍵,建立索引獲取對象。
IDBIndex 是持久性的鍵值對存儲。只要插入、更新或刪除數(shù)據(jù)記錄,引用的對象庫中的記錄,索引就會自動更新。
IDBObjectStore.index()
方法可以獲取 IDBIndex 對象。
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
為數(shù)組的情況,如果設(shè)為true
,創(chuàng)建數(shù)組時,每個數(shù)組成員都會有一個條目,否則每個數(shù)組都只有一個條目。IDBIndex.unique
:布爾值,表示創(chuàng)建索引時是否允許相同的主鍵。
IDBIndex 對象有以下方法,它們都是異步的,立即返回的都是一個 IDBRequest 對象。
IDBIndex.count()
:用來獲取記錄的數(shù)量。它可以接受主鍵或 IDBKeyRange 對象作為參數(shù),這時只返回符合主鍵的記錄數(shù)量,否則返回所有記錄的數(shù)量。IDBIndex.get(key)
:用來獲取符合指定主鍵的數(shù)據(jù)記錄。IDBIndex.getKey(key)
:用來獲取指定的主鍵。IDBIndex.getAll()
:用來獲取所有的數(shù)據(jù)記錄。它可以接受兩個參數(shù),都是可選的,第一個參數(shù)用來指定主鍵,第二個參數(shù)用來指定返回記錄的數(shù)量。如果省略這兩個參數(shù),則返回所有記錄。由于獲取成功時,瀏覽器必須生成所有對象,所以對性能有影響。如果數(shù)據(jù)集比較大,建議使用 IDBCursor 對象。IDBIndex.getAllKeys()
:該方法與IDBIndex.getAll()
方法相似,區(qū)別是獲取所有主鍵。IDBIndex.openCursor()
:用來獲取一個 IDBCursor 對象,用來遍歷索引里面的所有條目。IDBIndex.openKeyCursor()
:該方法與IDBIndex.openCursor()
方法相似,區(qū)別是遍歷所有條目的主鍵。
IDBCursor 對象
IDBCursor 對象代表指針對象,用來遍歷數(shù)據(jù)倉庫(IDBObjectStore)或索引(IDBIndex)的記錄。
IDBCursor 對象一般通過IDBObjectStore.openCursor()
方法獲得。
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(從頭開始向后遍歷,重復(fù)的值只遍歷一次)、prev(從尾部開始向前遍歷)、prevunique(從尾部開始向前遍歷,重復(fù)的值只遍歷一次)。該屬性通過IDBObjectStore.openCursor()
方法的第二個參數(shù)指定,一旦指定就不能改變了。IDBCursor.key
:返回當(dāng)前記錄的主鍵。IDBCursor.value
:返回當(dāng)前記錄的數(shù)據(jù)值。IDBCursor.primaryKey
:返回當(dāng)前記錄的主鍵。對于數(shù)據(jù)倉庫(objectStore)來說,這個屬性等同于 IDBCursor.key;對于索引,IDBCursor.key 返回索引的位置值,該屬性返回?cái)?shù)據(jù)記錄的主鍵。
IDBCursor 對象有如下方法。
IDBCursor.advance(n)
:指針向前移動 n 個位置。IDBCursor.continue()
:指針向前移動一個位置。它可以接受一個主鍵作為參數(shù),這時會跳轉(zhuǎn)到這個主鍵。IDBCursor.continuePrimaryKey()
:該方法需要兩個參數(shù),第一個是key
,第二個是primaryKey
,將指針移到符合這兩個參數(shù)的位置。IDBCursor.delete()
:用來刪除當(dāng)前位置的記錄,返回一個 IDBRequest 對象。該方法不會改變指針的位置。IDBCursor.update()
:用來更新當(dāng)前位置的記錄,返回一個 IDBRequest 對象。它的參數(shù)是要寫入數(shù)據(jù)庫的新的值。
IDBKeyRange 對象
IDBKeyRange 對象代表數(shù)據(jù)倉庫(object store)里面的一組主鍵。根據(jù)這組主鍵,可以獲取數(shù)據(jù)倉庫或索引里面的一組記錄。
IDBKeyRange 可以只包含一個值,也可以指定上限和下限。它有四個靜態(tài)方法,用來指定主鍵的范圍。
IDBKeyRange.lowerBound()
:指定下限。IDBKeyRange.upperBound()
:指定上限。IDBKeyRange.bound()
:同時指定上下限。IDBKeyRange.only()
:指定只包含一個值。
下面是一些代碼實(shí)例。
// 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()
這三個方法默認(rèn)包括端點(diǎn)值,可以傳入一個布爾值,修改這個屬性。
與之對應(yīng),IDBKeyRange 對象有四個只讀屬性。
IDBKeyRange.lower
:返回下限IDBKeyRange.lowerOpen
:布爾值,表示下限是否為開區(qū)間(即下限是否排除在范圍之外)IDBKeyRange.upper
:返回上限IDBKeyRange.upperOpen
:布爾值,表示上限是否為開區(qū)間(即上限是否排除在范圍之外)
IDBKeyRange 實(shí)例對象生成以后,將它作為參數(shù)輸入 IDBObjectStore 或 IDBIndex 對象的openCursor()
方法,就可以在所設(shè)定的范圍內(nèi)讀取數(shù)據(jù)。
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 有一個實(shí)例方法includes(key)
,返回一個布爾值,表示某個主鍵是否包含在當(dāng)前這個主鍵組之內(nèi)。
var keyRangeValue = IDBKeyRange.bound('A', 'K', false, false);
keyRangeValue.includes('F') // true
keyRangeValue.includes('W') // false
參考鏈接
- Raymond Camden, Working With IndexedDB – Part 1
- Raymond Camden, Working With IndexedDB – Part 2
- Raymond Camden, Working With IndexedDB - Part 3
- Tiffany Brown, An Introduction to IndexedDB
- David Fahlander, Breaking the Borders of IndexedDB
- TutorialsPoint, HTML5 - IndexedDB
更多建議: