一创泄、 indexDB簡介
indexDB是為了解決h5的localStorage在容量敢靡,存儲類型上的限制而提出的web客戶端數(shù)據(jù)庫素标,與localStorage最大的不同在于其更多的從數(shù)據(jù)庫的角度對API進行了設計猪瞬,已在safari7.1, IE10膀懈,chrome23以上的版本中得到實現(xiàn)仅仆。該API規(guī)范提出了以下對象
- 數(shù)據(jù)庫:IDBDatabase 對象
- 對象倉庫:IDBObjectStore 對象
- 索引: IDBIndex 對象
- 事務: IDBTransaction 對象
- 操作請求:IDBRequest 對象
- 指針: IDBCursor 對象
- 主鍵集合:IDBKeyRange 對象
- 首先通過indexDB.open指定名稱和版本打開數(shù)據(jù)庫墓拜,獲取IDBRequst對象港柜,后續(xù)過程為異步;
- 如果版本號增加則會發(fā)生onupgradeneeded事件咳榜,否則僅發(fā)生success事件夏醉,可在該事件中通過request.result或e.target.result獲取到IDBDatabase對象,即數(shù)據(jù)庫本身涌韩;
- 獲取數(shù)據(jù)庫后畔柔,可通過objectStoreName.contains檢查數(shù)據(jù)庫存儲的表,如果沒有需要查詢的表可通過createObjectStore新建表贸辈,新建時如不指明keyPath則默認為id字段遞增释树;如果存在可以直接通過objectStore獲取指定的IDBObjectStore表對象;
- 獲取表后擎淤,可通過createIndex建立索引奢啥;
- 如需對表進行增刪改查操作,必須借由事務API進行嘴拢,需要調用db.transcation(storeNames桩盲,operationType)獲取指定表序列執(zhí)行指定操作的IDBTransaction對象,operationType目前僅有readwrite和readonly兩種席吴;
- 獲取事務對象后赌结,先通過objectStore指定操作的表,然后通過add/delete/put/get執(zhí)行相應的增刪改查操作孝冒,該操作為異步柬姚,只可以通過注冊success和error的事件監(jiān)聽函數(shù)執(zhí)行回調。
二庄涡、代碼解析
以下內容為對indexDB驅動相關代碼的流程分析量承,以及相關兼容代碼的原理,可供indexDB開發(fā)者作為參考,localForage結合promise對于indexDB做了一系列的異步事務調度管理策略撕捍,確保多forage實例拿穴,多數(shù)據(jù)庫,多objectStorage表可協(xié)同工作忧风,不會引發(fā)數(shù)據(jù)庫一致性問題默色。
1. blob兼容
blob兼容主要分為兩種情況:
- blob文件能否存入indexDB (chrome < 37 || android < 5不支持)
- blob文件從indexDB取出使用是否會出現(xiàn)問題 (chrome 37~42 存在解碼問題)
相關參考信息:
- content-type bug: https://code.google.com/p/chromium/issues/detail?id=408120
- 404 bug: https://code.google.com/p/chromium/issues/detail?id=447916
- FileReader bug: https://code.google.com/p/chromium/issues/detail?id=447836
localForage的兼容方案如下圖所示:
- 針對第一種情況,localForage在每次建立新的db時都會專門新建一個名為local-forage-detect-blob-support的表(額外建表為防止調用iterate或length等接口產(chǎn)生干擾)來嘗試存儲一個blob文件狮腿,若該過程失敗則直接返回false腿宰,緩存到supportsBlobs變量中
- 針對第二種情況,在事務的oncomplete事件中檢查瀏覽器版本蚤霞,如果為chrome 43以上版本或edge(edge接入谷歌內核后需單獨在userAgent里check)或其他瀏覽器酗失,則可完美支持,返回ture昧绣,否則返回false规肴,緩存到supportsBlobs
-
之后檢查文件類型支持時只需訪問supportsBlobs本地變量即可
對于無法完美支持blob存儲indexDB的情況, localForage采用的向下兼容策略為:
- 使用FileReader和btoa將二進制文件轉換為base64二進制字符串進行存儲,并添加一個轉換的標記
- 取用時通過_isEncodedBlob似有方法檢查是否為轉碼字符串
- 如果是轉碼字符串夜畴,則再將其轉換回binary buffer數(shù)組
- 通過createBlob方法重建二進制文件(構造器優(yōu)先級為: Blob > BloblBuilder > MSBlobBuilder > MozBlobBuilder > WebKitBlobBuilder, 使用Builder時需通過getBlob接口s獲取指定type的Blob文件)
2. 數(shù)據(jù)庫驅動
localForage對于indexDB數(shù)據(jù)庫驅動較為復雜拖刃,主要原因為兼容多forage同時操作多個db數(shù)據(jù)庫,因而需處理復雜的數(shù)據(jù)庫upgradeneeded事件贪绘,并對每個數(shù)據(jù)庫的事務隊列進行同步化的管理兑牡,完整流程如下圖所示:Step1 實例初始化
- 通過_initStorage方法創(chuàng)建forage存儲實例,該實例對應指定數(shù)據(jù)庫(name)的指定數(shù)據(jù)表(storeName)税灌,每個實例根據(jù)option的設置(name均函,version,storeName等)建立一個對應的本地記錄_dbInfo
- 根據(jù)這個實例的name屬性獲取對應的數(shù)據(jù)庫上下文dbContext菱涤,若未發(fā)現(xiàn)該數(shù)據(jù)庫上下文則通過createDbContext新建一個苞也,forages存儲其關聯(lián)的存儲實例,db為對應的數(shù)據(jù)庫連接(初始化為null, dbReady為事務鏈 , deferredOperations保存延時執(zhí)行的操作粘秆,除初始化和銷毀事務鏈外較少使用
- 獲取到數(shù)據(jù)庫上下文后如迟,根據(jù)options.version分析該數(shù)據(jù)庫的版本是否存在且最新,根據(jù)options.storeName判斷該數(shù)據(jù)庫是否存在用戶指定的數(shù)據(jù)表攻走,上述條件有一個不符合殷勘,則更新數(shù)據(jù)庫連接
- 之后, 通過getConnection方法獲取數(shù)據(jù)庫連接,如無需升級則直接從dbContext.db中獲取昔搂,如需升級玲销,則關閉已有連接,增加數(shù)據(jù)庫版本摘符,添加指定的數(shù)據(jù)表痒玩,觸發(fā)數(shù)據(jù)庫onupgradeneed事件淳附,在其回調中獲取數(shù)據(jù)庫連接议慰,并同步到dbContext和dbInfo中
Step2 事務處理
localForage主要使用promise鏈來管理事務蠢古,核心代碼如下:
function _deferReadiness(dbInfo) {
var dbContext = dbContexts[dbInfo.name];
// Create a deferred object representing the current database operation.
var deferredOperation = {};
deferredOperation.promise = new Promise(function(resolve, reject) {
deferredOperation.resolve = resolve;
deferredOperation.reject = reject;
});
// Enqueue the deferred operation.
dbContext.deferredOperations.push(deferredOperation);
// Chain its promise to the database readiness.
if (!dbContext.dbReady) {`
dbContext.dbReady = deferredOperation.promise;
} else {
dbContext.dbReady = dbContext.dbReady.then(function() {
return deferredOperation.promise;
});
}
}
通過這部分代碼可以看到,在數(shù)據(jù)庫初始化時别凹,會建立一個事務鏈的promise對象保存到dbReady里草讶,之后所有新的事務都注冊到dbReady的then方法中,并在其resolve時替換鏈尾炉菲,實現(xiàn)循環(huán)調用堕战,這一設計十分巧妙。
與初始化時的場景類似拍霜,處理事務時也需要照顧到數(shù)據(jù)庫版本更新的問題嘱丢,同時還需要考慮到更新的數(shù)據(jù)表不在數(shù)據(jù)庫(被先前的其他實例刪掉)的異常情況,如存在問題祠饺,則同樣需觸發(fā)數(shù)據(jù)庫升級越驻,并通過tryReconnect重新建立連接,同步dbContext, dbInfo道偷,初始化事務鏈缀旁,再進行操作事務的注冊。
Step3 數(shù)據(jù)庫銷毀
所對應流程圖右下角的內容勺鸦,主要為通過indexDB.deleteDatabase方法對特定數(shù)據(jù)庫進行刪除并巍,同時通過_advanceReadiness或_rejectReadiness預先執(zhí)行事務鏈尾promise的回調(視運行時狀態(tài)而定),從而清空事務鏈换途,完成整個銷毀過程懊渡。