浏览器数据库IndexedDB
概述
隨著瀏覽器的功能不斷增強,越來越多的網站開始考慮,將大量數據儲存在客戶端,這樣可以減少從服務器獲取數據,直接從本地獲取數據。
Cookie 的大小不超過4KB,且每次請求都會發送回服務器;
LocalStorage 在 2.5MB 到 10MB 之間(各家瀏覽器不同),而且不提供搜索功能,不能建立自定義的索引。
所以,需要一種新的解決方案,這就是 IndexedDB 誕生的背景。
IndexedDB 就是瀏覽器提供的本地數據庫,它可以被網頁腳本創建和操作。IndexedDB 允許儲存大量數據,提供查找接口,還能建立索引,就數據庫類型而言,IndexedDB 不屬于關系型數據庫(不支持 SQL 查詢語句)。
indexedDB具有以下特點
基本概念
IndexedDB是一個比較復雜的API,它把不同的實體,抽象成一個個對象接口。
- 數據庫:IDBDatabase 對象
- 對象倉庫:IDNObjectStore 對象
- 索引:IDBIndex 對象
- 事務:IDBTransaction 對象
- 操作請示:IDBRequest 對象
- 指針:IDBCursor 對象
- 主鍵集合:IDBKeyRange 對象
數據庫
數據庫是一系列相關數據的容器。每個域名(協議+域名+端口號)都可以新建任意多個數據庫。
IndexedDB數據庫有版本的概念。同一個時刻,只能有一個版本的數據庫存在。如果要修改數據庫結構(新增或刪除表、索引或者主鍵),只能通過升級數據庫版本完成。
對象倉庫
每個數據庫包含若干個對象倉庫(object store)。它類似于關系型數據庫的表格。
數據記錄
對象倉庫保存的是數據記錄。每條記錄類似于關系型數據庫的行,但是只有主鍵和數據體兩部分。主鍵用來建立默認的索引,必須是不同的,否則會報錯。主鍵可以是數據記錄里面的一個屬性,也可以指定為一個遞增的整數編號。
{ id: 1, text: 'foo' }上面的對象中,id屬性可以當作主鍵。
數據體可以是任意數據類型,不限于對象。
索引
為了加速數據的檢索,可以在對象倉庫里面,為不同的屬性建立索引。
事務
數據記錄的讀寫和刪改,都要通過事務完成。事務對象提供error、abort和complete三個事件,用來監聽操作結果。
操作流程
打開數據庫:indexedDB.open()
var request = window.indexedDB.open(databaseName, version);這個方法接受兩個參數,第一個參數是字符串,表示數據庫的名字。如果指定的數據庫不存在,就會新建數據庫。第二個參數是整數,表示數據庫的版本。如果省略,打開已有數據庫時,默認為當前版本;新建數據庫時,默認為1。
indexedDB.open()方法返回一個 IDBRequest 對象。這個對象通過三種事件error、success、upgradeneeded,處理打開數據庫的操作結果。
(1)error 事件
error事件表示打開數據庫失敗。
request.onerror = function (event) {console.log('數據庫打開報錯'); };(2)success 事件
success事件表示成功打開數據庫。
var db;request.onsuccess = function (event) {db = request.result;console.log('數據庫打開成功'); };(3)upgradeneeded 事件
如果指定的版本號,大于數據庫的實際版本號,就會發生數據庫升級事件upgradeneeded。
var db;request.onupgradeneeded = function (event) {db = event.target.result; }這時通過事件對象的target.result屬性,拿到數據庫實例。
新建數據庫
新建數據庫與打開數據庫是同一個操作。如果指定的數據庫不存在,就會新建。不同之處在于,后續的操作主要在upgradeneeded事件的監聽函數里面完成,因為這時版本從無到有,所以會觸發這個事件。
通常,新建數據庫以后,第一件事是新建對象倉庫(即新建表)。
request.onupgradeneeded = function(event) {db = event.target.result;var objectStore = db.createObjectStore('person', { keyPath: 'id' }); }上面代碼中,數據庫新建成功以后,新增一張叫做person的表格,主鍵是id。
新建對象倉庫以后,下一步可以新建索引。
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()的三個參數分別為索引名稱、索引所在的屬性、配置對象(說明該屬性是否包含重復的值)。
新增數據
新增數據指的是向對象倉庫寫入數據記錄。通過事務完成。
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事件,了解是否寫入成功。
讀取數據
讀取數據也是通過事務完成。
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();遍歷數據
遍歷數據表格的所有記錄,要使用指針對象 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('沒有更多數據了!');}}; }readAll();上面代碼中,新建指針對象的openCursor()方法是一個異步操作,所以要監聽success事件。
更新數據
更新數據要使用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('數據更新成功');};request.onerror = function (event) {console.log('數據更新失敗');} }update();上面代碼中,put()方法自動更新了主鍵為1的記錄。
刪除數據
IDBObjectStore.delete()方法用于刪除記錄。
function remove() {var request = db.transaction(['person'], 'readwrite').objectStore('person').delete(1);request.onsuccess = function (event) {console.log('數據刪除成功');}; }remove();使用索引
索引的意義在于,可以讓你搜索任意字段,也就是說從任意字段拿到數據記錄。如果不建立索引,默認只能搜索主鍵(即從主鍵取值)。
假定新建表格的時候,對name字段建立了索引。
objectStore.createIndex('name', 'name', { unique: false });現在,就可以從name找到對應的數據記錄了。
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.open():打開數據庫
indexedDB.open()方法用于打開數據庫。這是一個異步操作,但是會立刻返回一個IDBOpenDBRequest對象。
var openRequest = window.indexedDB.open('test', 1);上面代碼表示,打開一個名為test、版本為1的數據庫。如果該數據庫不存在,則會新建該數據庫。
open()方法的第一個參數是數據庫名稱,格式為字符串,不可省略;第二個參數是數據庫版本,是一個大于0的正整數(0將報錯),如果該參數大于當前版本,會觸發數據庫升級。第二個參數可省略,如果數據庫已存在,將打開當前版本的數據庫;如果數據庫不存在,將創建該版本的數據庫,默認版本為1。
打開數據庫是異步操作,通過各種事件通知客戶端。下面是有可能觸發的4種事件。
- success:打開成功。
- error:打開失敗。
- upgradeneeded:第一次打開該數據庫,或者數據庫版本發生變化。
- blocked:上一次的數據庫連接還未關閉。
第一次打開數據庫時,會先觸發upgradeneeded事件,然后觸發success事件。
根據不同的需要,對上面4種事件監聽函數。
indexedDB.deleteDatabase():刪除數據庫
indexedDB.deleteDatabase()方法用于刪除一個數據庫,參數為數據庫的名字。它會立刻返回一個IDBOpenDBRequest對象,然后對數據庫執行異步刪除。刪除操作的結果會通過事件通知,IDBOpenDBRequest對象可以監聽以下事件。
- success:刪除成功
- error:刪除報錯
調用deleteDatabase()方法以后,當前數據庫的其他已經打開的連接都會接收到versionchange事件。
注意,刪除不存在的數據庫并不會報錯。
indexedDB.cmp():比較兩個值是否為IndexedDB的相同的主鍵
indexedDB.cmp()方法比較兩個值是否為 indexedDB 的相同的主鍵。它返回一個整數,表示比較的結果:0表示相同,1表示第一個主鍵大于第二個主鍵,-1表示第一個主鍵小于第二個主鍵。
window.indexedDB.cmp(1, 2) // -1注意,這個方法不能用來比較任意的 JavaScript 值。如果參數是布爾值或對象,它會報錯。
window.indexedDB.cmp(1, true) // 報錯 window.indexedDB.cmp({}, {}) // 報錯IDBRequest 對象:表示打開的數據庫連接
indexedDB.open()方法和indexedDB.deleteDatabase()方法會返回這個對象。數據庫的操作都是通過這個對象完成的。
這個對象的所有操作都是異步操作,要通過readyState屬性判斷是否完成,如果為pending就表示操作正在進行,如果為done就表示操作完成,可能成功也可能失敗。
操作完成以后,觸發success事件或error事件,這時可以通過result屬性和error屬性拿到操作結果。如果在pending階段,就去讀取這兩個屬性,是會報錯的。
屬性
Iquest.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對象。后面的數據庫操作都是通過這個對象完成。
屬性
IDBDatabase.name
:string,數據庫名稱。
IDBDatabase.version
:int,數據庫版本。數據庫第一次創建時,該屬性為空字符串。
IDBDatabase.objectStoreNames
:DOMStringList對象(字符串的集合),包含當前時間的所有object store的名字。
IDBDatabase.onabort
:指定abort時間(事務中止)的監聽函數。
IDBDatabase.onclose
:指定close時間(數據庫意外關閉)的監聽函數。
IDBDatabase.onerror
:指定error時間(訪問數據庫失敗)的監聽函數。
IDBDatabase.onversionchange
:數據庫版本發生變化時觸發(發生upgradeneeded事件,或調用indexedDB.deleteDatabase())。
方法
IDBDatabase對象有以下方法。
IDBDatabase.close()
:關閉數據庫連接,實際會等所有事務完成后再關閉。
IDBDatabase.createObjectStore()
:創建存放數據的對象倉庫,類似于傳統關系型數據庫的表格,返回一個IDBObjectStore對象,該方法只能在versionchange事件監聽函數中調用。
IDBDatabase.transaction()
:返回一個IDBTransaction事務對象。
IDBObjectStore對象
IDBObjectStore對象對應一個對象倉庫。IDBDatabase.createObjectStore()方法返回的就是一個IDBObjectStore對象。
IDBDatabase對象的transaction()返回一個事務對象,該方法的objectStore()方法返回一個IDBObjectStore對象。
屬性
IDBObjectStore.indexNames
:返回一個類似數組的對象(DOMStringList),包含了當前對象倉庫的所有索引。
IDBObjectStore.keyPath
:返回當前對象倉庫的主鍵。
IDBObjectStore.name
:返回當前對象倉庫的名稱。
IDBObjectStore.transaction
:返回當前對象倉庫所屬的事務對象。
IDBObjectStore.autoIncrement
:布爾值,表示主鍵是否會自動遞增。
方法
IDBObjectStore.add()
用于向對象倉庫添加數據,返回一個IDBRequest對象。該方法只用于添加數據,如果主鍵相同會報錯,因此更新數據必須用put()方法。
該方法接受兩個參數,第一個參數是鍵值,第二個參數是主鍵,該參數可選,如果省略默認為null。
創建事務以后,就可以獲取對象倉庫,然后使用add()方法往里面添加數據了。
IDBObjectStore.put()
用于更新某個主鍵對應的數據記錄,如果對應的鍵值不存在,則插入一條新的記錄。還方法返回一個IDBRequest對象。
該方法接受兩個參數,第一個參數為新數據,第二個參數為主鍵,該參數可選,且只在自動遞增時才有必要提供,因為那時主鍵不包含在數據值里面。
IDBObjectStore.clear()
刪除當前倉庫對象的所有記錄。該方法返回一個IDBRequest對象。
IDBObjectStore.delete()
用于刪除指定主鍵的記錄。該方法返回一個IDBRequest對象。
該方法的參數為主鍵的值。
IDBObjectStore.count()
用于計算記錄的數量。該方法返回一個IDBRequest對象。
不帶參數時,該方法返回當前對象倉庫的所有記錄數量。如果主鍵或 IDBKeyRange 對象作為參數,則返回對應的記錄數量。
IDBObjectStore.getKey()
用于獲取主鍵。該方法返回一個 IDBRequest 對象。
該方法的參數可以是主鍵值或 IDBKeyRange 對象。
IDBObjectStore.get()
用于獲取主鍵對應的數據記錄。該方法返回一個 IDBRequest 對象。
get()是異步方法,讀取成功以后,只能在success事件的監聽函數中處理數據。
IDBObjectStore.getAll()
用于獲取對象倉庫的記錄。該方法返回一個 IDBRequest 對象。
IDBObjectStore.getAllKeys()
用于獲取所有符合條件的主鍵。該方法返回一個 IDBRequest 對象。
IDBObjectStore.index()
返回指定名稱的索引對象 IDBIndex。
有了索引以后,就可以針對索引所在的屬性讀取數據。
IDBObjectStore.createIndex()
用于新建當前數據庫的一個索引。該方法只能在VersionChange監聽函數里面調用。
objectStore.createIndex(indexName, keyPath, objectParameters)該方法可以接受三個參數。
- indexName:索引名
- keyPath:主鍵
- objectParameters:配置對象(可選)
第三個參數可以配置以下屬性。
- unique:如果設為true,將不允許重復的值
- multiEntry:如果設為true,對于有多個值的主鍵數組,每個值將在索引里面新建一個條目,否則主鍵數組對應一個條目。
IDBObjectStore.deleteIndex()
用于獲取一個指針對象。
指針對象可以用來遍歷數據。該對象也是異步的,有自己的success和error事件,可以對它們指定監聽函數。
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。后兩個值表示如果遇到重復值,會自動跳過。
IDBObjectStore.openKeyCursor()
用于獲取一個主鍵指針對象。
IDBTransaction 對象
IDBTransaction 對象用來異步操作數據庫事務,所有的讀寫操作都要通過這個對象進行。
IDBDatabase.transaction()方法返回的就是一個 IDBTransaction 對象。
事務的執行順序是按照創建的順序,而不是發出請求的順序。
事務有可能失敗,只有監聽到事務的complete事件,才能保證事務操作成功。
屬性
IDBTransaction.db
:返回當前事務所在的數據庫對象IDBDatabase。
IDBTransaction.error
:返回當前事務的錯誤。如果事務沒有結束,或者事務成功結束,或者被手動終止,該方法返回null。
IDBTransaction.mode
:返回當前事務的模式,默認是readonly,另一個值是readwrite。
IDBTransaction.objectStoreNames
:返回一個類似數組的對象DOMStringList,成員是當前事務涉及的對象倉庫的名字。
IDBTransaction.onabort
:指定abort事件(事務中斷)的監聽函數。
IDBTransaction.oncomplete
:指定complete事件(事務成功)的監聽函數。
IDBTransaction.onerror
:指定error事件(事務失敗)的監聽事件。
方法
IDBTransaction.abort()
:終止當前事務,回滾所有已經進行的變更。
IDBTransaction.objectStore(name)
:返回指定名稱的對象倉庫IDBObjectStore。
IDBIndex對象
IDBIndex對象代表數據庫的索引,通過這個對象可以獲取數據庫里面的記錄。數據記錄的主鍵默認就是帶有索引,IDBIndex對象主要用于通過除主鍵外的其他鍵,建立索引獲取對象。
IDBIndex是持久性的鍵值對存儲。只要插入、更新或刪除數據記錄,引用的對象庫中的記錄,索引就會自動更新。
IDBObjectStore.index()方法可以獲取 IDBIndex 對象。
屬性
IDBIndex.name
:string,索引的名稱。
IDBIndex.objectStore
:索引所在的對象倉庫。
IDBIndex.keyPath
:索引的主鍵。
IDBIndex.multiEntry
:Boolean,表示創建索引時是否允許相同的主鍵。
方法
都是異步方法,立即返回的都是一個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()方法獲得。
屬性
IDBCursor.source
:返回正在遍歷的對象倉庫或索引。
IDBCursor.direction
:string,表示指針遍歷的方向。
共有四個可能的值:
- next(從頭開始向后遍歷)、
- nextunique(從頭開始向后遍歷,重復的值只遍歷一次)、
- prev(從尾部開始向前遍歷)、
- prevunique(從尾部開始向前遍歷,重復的值只遍歷一次)。
該屬性通過IDBObjectStore.openCursor()方法的第二個參數指定,一旦指定就不能改變了。
IDBCursor.key
返回當前記錄的主鍵。
IDBCursor.value
返回當前記錄的數據值。
IDBCursor.primaryKey
返回當前記錄的主鍵。
對于數據倉庫(ObjectStore)來說,這個屬性等同于IDBCursor.key;對于索引,IDBCursor.key返回索引的位置值,該屬性返回數據記錄的主鍵。
方法
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():指定只包含一個值。
IDBKeyRange.lowerBound()、IDBKeyRange.upperBound()、IDBKeyRange.bound()這三個方法默認包括端點值,可以傳入一個布爾值,修改這個屬性。
屬性
都為只讀屬性
IDBKeyRange.lower
:返回下限
IDBKeyRange.lowerOpen
:Boolean,表示下限是否為開區間。
IDBKeyRange.upper
:返回上限
IDBKeyRange.upperOpen
:Boolean,表示上限是否為開區間。
方法
IDBKeyRange.includes(key)
:返回一個布爾值,表示某個主鍵是否包含在當前這個主鍵組之內。
總結
以上是生活随笔為你收集整理的浏览器数据库IndexedDB的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: displaytag 的使用
- 下一篇: win10cmd切换目录