新聞中心
在html 5中,其中一個引人注意的新特性,那就是允許使用Indexed DB。用戶可以從這個鏈接(http://www.w3.org/TR/IndexedDB/)了解到Indexed DB的詳細標準。在本文中,將對Indexed DB作簡單的入門介紹。

專注于為中小企業(yè)提供成都做網(wǎng)站、網(wǎng)站建設(shè)服務(wù),電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)墨玉免費做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了近千家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實現(xiàn)規(guī)模擴充和轉(zhuǎn)變。
概述
從本質(zhì)上說,IndexedDB允許用戶在瀏覽器中保存大量的數(shù)據(jù)。任何需要發(fā)送大量數(shù)據(jù)的應(yīng)用都可以得益于這個特性,可以把數(shù)據(jù)存儲在用戶的瀏覽器端。當前這只是IndexedDB的其中一項功能,IndexedDB也提供了強大的基于索引的搜索api功能以獲得用戶所需要的數(shù)據(jù)。
用戶可能會問:IndexedDB是和其他以前的存儲機制(如cookie,session)有什么不同?
Cookies是最常用的瀏覽器端保存數(shù)據(jù)的機制,但其保存數(shù)據(jù)的大小有限制并且有隱私問題。Cookies并且會在每個請求中來回發(fā)送數(shù)據(jù),完全沒辦法發(fā)揮客戶端數(shù)據(jù)存儲的優(yōu)勢。
再來看下Local Storage本地存儲機制的特點。Local Storage在HTML 5中有不錯的支持,但就總的存儲量而言依然是有所限制的。Local Storage并不提供真正的“檢索API”,本地存儲的數(shù)據(jù)只是通過鍵值對去訪問。Local Storage對于一些特定的需要存儲數(shù)據(jù)的場景是很適合的,例如,用戶的喜好習慣,而IndexedDB則更適合存儲如廣告等數(shù)據(jù)(它更象一個真正的數(shù)據(jù)庫)。
在我們進一步探討Indexed DB前,我們先看下目前的主流瀏覽器對Indexed DB的支持。 IndexedDB目前依然是一個w3c中候選的建議規(guī)范,在這一點上規(guī)范目前還是令人感到滿意的,但現(xiàn)在正在尋找開發(fā)者社區(qū)的反饋。該規(guī)范可能會在到***確認階段前會因應(yīng)w3c的建議有所變化。在一般情況下,目前的瀏覽器對IndexedDB的支持都以比較統(tǒng)一的方式實現(xiàn),但開發(fā)者應(yīng)注意在未來的更新及對此作出一定的修改。
我們來看來自CanIUse.com的對于各瀏覽器對IndexedDB的支持的圖表,可以看到,目前桌面端瀏覽器對其的支持是不錯的,但移動端瀏覽器的支持就比較少了:
Chrome for Android支持IndexedDB,但很少人目前在Android設(shè)備上使用這款瀏覽器。是否缺乏移動端瀏覽器的支持就意味著不應(yīng)該使用它呢?當然不是!幸好我們的開發(fā)者都懂得持續(xù)改進的概念。象IndexedDB這樣的特性可以用其他的方式添加到那些不支持該功能的瀏覽器中。用戶可以使用包裝過的類庫去轉(zhuǎn)換到移動端的WebSQL,又或者干脆不在移動端進行本地存儲數(shù)據(jù)。我個人認為能在客戶端緩存大量的數(shù)據(jù),對使用上來說是很重要的功能,即使缺乏移動端的支持。
開始學習
首先,在使用IndexedDB前,要做的是檢查當前的瀏覽器對IndexedDB是否支持,做法只需要使用如下代碼就可以實現(xiàn):
- document.addEventListener("DOMContentLoaded", function(){
- if("indexedDB" in window) {
- console.log("YES!!! I CAN DO IT!!! WOOT!!!");
- } else {
- console.log("I has a sad.");
- }
- },false);
上面的代碼中(可以在本文下載代碼中的test1.html中找到),使用了DOMContentLoaded事件在加載的過程中,通過判斷在window對象中是否存在indexedDB,當然為了在接下來的過程中記住判斷的最終結(jié)果,可以使用如下的代碼更好地保存(test2.html):
- var idbSupported = false;
- document.addEventListener("DOMContentLoaded", function(){
- if("indexedDB" in window) {
- idbSupported = true;
- }
- },false);
操作數(shù)據(jù)庫
下面要講解的是如何操作IndexedDB數(shù)據(jù)庫。首先要了解的是,IndexedDB并不象傳統(tǒng)的如SQL Server那樣需要額外安裝。Indexed是存在于瀏覽器端的并且能被用戶所訪問控制。IndexedDB和cookies和local storage的原則是一樣的,就是一個IndexedDB是和一個唯一的DOMAIN相關(guān)聯(lián)的。比如名為“Foo”的數(shù)據(jù)庫是由foo.com所關(guān)聯(lián)的,是不會和goo.com所創(chuàng)建的同名“Foo”數(shù)據(jù)庫沖突的,因為他們屬于不同的domain,并且他們之間是不能互相訪問的。
打開一個數(shù)據(jù)庫是通過命令執(zhí)行的?;镜挠梅ㄊ翘峁?shù)據(jù)庫的名稱和版本號即可,其中版本是十分重要的,稍候會作解析。下面是基本的例子:
- var openRequest = indexedDB.open("test",1);
打開一個IndexedDB數(shù)據(jù)庫是異步的操作。為了處理操作的返回結(jié)果,需要增加一些事件的監(jiān)聽,目前有四種不同類型的事件監(jiān)聽事件:
- success
- error
- upgradeneeded
- blocked
大家可能已經(jīng)能知道success和error事件的含義了。而upgradeneeded事件是在***打開數(shù)據(jù)庫或者改變數(shù)據(jù)庫版本的時候被觸發(fā)。blocked事件是在前一個連接沒有被關(guān)閉的時候被觸發(fā)。
讓我們看下接下來的例子(test3.html),其中當***訪問網(wǎng)站的時候會觸發(fā)upgradeneeded事件,然后是success事件。
- var idbSupported = false;
- var db;
- document.addEventListener("DOMContentLoaded", function(){
- if("indexedDB" in window) {
- idbSupported = true;
- }
- if(idbSupported) {
- var openRequest = indexedDB.open("test",1);
- openRequest.onupgradeneeded = function(e) {
- console.log("Upgrading...");
- }
- openRequest.onsuccess = function(e) {
- console.log("Success!");
- db = e.target.result;
- }
- openRequest.onerror = function(e) {
- console.log("Error");
- console.dir(e);
- }
- }
- },false);
#p#
在上面的代碼中,我們再一次檢查當前瀏覽器是否支持IndexedDB,如果支持,則打開一個數(shù)據(jù)庫。在這段代碼中我們使用了三個事件 ――upgrade事件、成功事件和錯誤事件。首先看success事件,該事件通過target.result傳入句柄,然后將其復制到一個全局變量db中,用作稍候添加數(shù)據(jù)用。如果用戶在支持IndexedDB的瀏覽器中運行上面的代碼,應(yīng)該會在控制臺看到看到Upgrading...和success的輸出信息,如果再次運行的話,則只會看到輸出成功的信息,因為upgradedneed事件只在***打開數(shù)據(jù)庫或升級的時候被調(diào)用。
對象存儲
接下來,我們學習如何存儲數(shù)據(jù)。IndexedDB有一個概念稱為“對象存儲”。
用戶可以認為這是一張典型的關(guān)系數(shù)據(jù)庫中的表。對象中當然會存儲了數(shù)據(jù),但也有一個keypath和可選的索引集合。所謂的Keypaths是用戶數(shù)據(jù)的唯一標識,并且可以用不同的格式表示。索引則在后面會進行講解。
還記得之前提到的upgrademeeded事件么?要注意的是只能在upgradeneeded事件中創(chuàng)建一個對象。目前,默認是在用戶***訪問你的網(wǎng)站的時候會自動創(chuàng)建對象。同時如果要修改你的對象,則必須更新數(shù)據(jù)的版本,并且要為此編寫代碼,讓我們來看代碼的例子如下:
- var idbSupported = false;
- var db;
- document.addEventListener("DOMContentLoaded", function(){
- if("indexedDB" in window) {
- idbSupported = true;
- }
- if(idbSupported) {
- var openRequest = indexedDB.open("test_v2",1);
- openRequest.onupgradeneeded = function(e) {
- console.log("running onupgradeneeded");
- var thisDB = e.target.result;
- if(!thisDB.objectStoreNames.contains("firstOS")) {
- thisDB.createObjectStore("firstOS");
- }
- }
- openRequest.onsuccess = function(e) {
- console.log("Success!");
- db = e.target.result;
- }
- openRequest.onerror = function(e) {
- console.log("Error");
- console.dir(e);
- }
- }
- },false);
上面的代碼(見test4.html)中,請看upgradeneeded事件,首先是通過變量thisDB獲得了打開的數(shù)據(jù)庫,這個變量的其中一個屬性是一個已存在的對象存儲的list,名為objectStoreNames。我們可以使用contains方法檢查某個對象是否已經(jīng)存在了,如果不存在則可以進行創(chuàng)建,使用的方法是createObjectStore,因為這個是IndexedDB的同步操作,因此不需要為此進行事件監(jiān)聽。
總結(jié)一下,當用戶訪問你的網(wǎng)站時,如果用戶的瀏覽器支持IndexedDB,則首先觸發(fā)的是upgradeneeded事件,代碼中檢查是否有“firstOS”對象存在,如果沒有的話則新創(chuàng)建一個,當用戶第二次訪問網(wǎng)站的時候,數(shù)據(jù)庫的版本依然和***次訪問時是相同的。
假如需要新增加另外一個對象存儲,則只需要增加版本號并復制上面contains/ createObjectStore部分的代碼,代碼如下(見test5.html):
- var openRequest = indexedDB.open("test_v2",2);
- openRequest.onupgradeneeded = function(e) {
- console.log("running onupgradeneeded");
- var thisDB = e.target.result;
- if(!thisDB.objectStoreNames.contains("firstOS")) {
- thisDB.createObjectStore("firstOS");
- }
- if(!thisDB.objectStoreNames.contains("secondOS")) {
- thisDB.createObjectStore("secondOS");
- }
- }
如何增加數(shù)據(jù)
下面我們可以開始增加數(shù)據(jù)。和傳統(tǒng)的關(guān)系型數(shù)據(jù)庫有點不同,IndexedDB允許保存對象。這意味著開發(fā)者甚至可以保存一個Javascript對象。對于數(shù)據(jù)查詢是需要使用事務(wù),事務(wù)需要兩個參數(shù),***個是將要處理的表的數(shù)組,其中的元素是表,第二個參數(shù)是事務(wù)的類型。目前有兩種事務(wù)的類型:只讀和讀寫。增加數(shù)據(jù)是屬于讀寫操作,代碼如下:
- var transaction = db.transaction(["people"],"readwrite");
這里指出了要設(shè)置存儲對象people為讀寫操作,然后使用objectStore指定要操作的存儲對象,存儲到變量store中去
- var store = transaction.objectStore("people");
接下來就可以增加數(shù)據(jù)了,我們定義一個person對象,然后增加到這個store中去:
- var person = {
- name:name,
- email:email,
- created:new Date()
- }
- var request = store.add(person,1);
可以看到,這里聲明了一個Javascript的普通對象,然后使用store的add方法就可以增加這個對象到對象存儲中,其中add的第2個參數(shù)是標識數(shù)據(jù)的唯一標識,在這里只是暫時硬編碼了,在接下來我們將不再使用硬編碼的方法。
要注意增加數(shù)據(jù)的操作是異步的,所以增加兩個事件監(jiān)聽,代碼如下:
- request.onerror = function(e) {
- console.log("Error",e.target.error.name);
- }
- request.onsuccess = function(e) {
- console.log("Woot! Did it");
- }
現(xiàn)在我們看下test6.html,這個是包含html的完整代碼:
- var db;
- function indexedDBOk() {
- return "indexedDB" in window;
- }
- document.addEventListener("DOMContentLoaded", function() {
- //No support? Go in the corner and pout.
- if(!indexedDBOk) return;
- var openRequest = indexedDB.open("idarticle_people",1);
- openRequest.onupgradeneeded = function(e) {
- var thisDB = e.target.result;
- if(!thisDB.objectStoreNames.contains("people")) {
- thisDB.createObjectStore("people");
- }
- }
- openRequest.onsuccess = function(e) {
- console.log("running onsuccess");
- db = e.target.result;
- //Listen for add clicks
- document.querySelector("#addButton").addEventListener("click", addPerson, false);
- }
- openRequest.onerror = function(e) {
- //Do something for the error
- }
- },false);
- function addPerson(e) {
- var name = document.querySelector("#name").value;
- var email = document.querySelector("#email").value;
- console.log("About to add "+name+"/"+email);
- var transaction = db.transaction(["people"],"readwrite");
- var store = transaction.objectStore("people");
- var person = {
- name:name,
- email:email,
- created:new Date()
- }
- var request = store.add(person,1);
- request.onerror = function(e) {
- console.log("Error",e.target.error.name);
- //some type of error handler
- }
- request.onsuccess = function(e) {
- console.log("Woot! Did it");
- }
- }
#p#
在瀏覽器中運行這段代碼,則可以看到如下圖的HTML頁面,其中在文本框輸入內(nèi)容,點按鈕,則會在瀏覽器的調(diào)試工具中(我們使用的是Chrome)看到有如下的輸出:
.如果使用Chrome,則可以充分利用其調(diào)試工具中對IndexedDB的可視化支持,如果點資源的TAB,展開IndexedDB的部分,則可以看到如下圖所示的,我們之前創(chuàng)建的people存儲對象。
接下來我們嘗試再次點擊增加按鈕,這個時候注意到會輸出錯誤的提示信息如下圖,其信息表示試圖增加一個已經(jīng)存儲的對象,違反數(shù)據(jù)約束了:
關(guān)于Key
Keys是IndexedDB的主鍵,傳統(tǒng)的關(guān)系數(shù)據(jù)庫可以沒有keys,但每個對象存儲都必須有一個key。IndexedDB允許多個不同類型的KEY。
***種方法是象上文中的那樣手工指定。第二種方法是使用keypath,其中的key是基于數(shù)據(jù)自身的屬性,比如people中的例子可以使用email地址作為key。第三種方法是建議采用的方法,就是使用key生成器,這有點象自動編號的主鍵的方法。下面是關(guān)于key的兩個例子,其中一個是使用keypath,另外的是使用key生成器:
- hisDb.createObjectStore("test", { keyPath: "email" });
- thisDb.createObjectStore("test2", { autoIncrement: true });
并且我們可以修改上一個例子中,用帶key的方法創(chuàng)建對象:
- thisDB.createObjectStore("people", {autoIncrement:true});
這樣的話,就可以取消之前硬編碼的做法了:
- var request = store.add(person);
讀取數(shù)據(jù)
再來看如何讀取數(shù)據(jù),讀取數(shù)據(jù)要在一個事務(wù)中進行并且是異步的,下面是例子:
- var transaction = db.transaction(["test"], "readonly");
- var objectStore = transaction.objectStore("test");
- var ob = objectStore.get(x);
- ob.onsuccess = function(e) {
- }
在上面的代碼中,首先設(shè)置為只讀的事務(wù)。然后使用objectStore.get方法就可以了,要注意編寫其onsuccess事件,也可以使用鏈式調(diào)用,如下:
- db.transaction(["test"], "readonly").objectStore("test").get(X).onsuccess = function(e) {}
現(xiàn)在我們在test8.html中,為我們之前的例子加上讀取數(shù)據(jù)的部分,運行后的效果如下圖:
其中讀取數(shù)據(jù)的代碼為:
- function getPerson(e) {
- var key = document.querySelector("#key").value;
- if(key === "" || isNaN(key)) return;
- var transaction = db.transaction(["people"],"readonly");
- var store = transaction.objectStore("people");
- var request = store.get(Number(key));
- request.onsuccess = function(e) {
- var result = e.target.result;
- console.dir(result);
- if(result) {
- var s = "
Key "+key+"
";
- for(var field in result) {
- s+= field+"="+result[field]+"
";- }
- document.querySelector("#status").innerHTML = s;
- } else {
- document.querySelector("#status").innerHTML = "
No match
";- }
- }
- }
#p#
如何讀取更多數(shù)據(jù)
如何讀取批量的數(shù)據(jù)?也就是象傳統(tǒng)關(guān)系數(shù)據(jù)庫中讀取數(shù)據(jù)集?IndexedDB支持游標的概念,這對有數(shù)據(jù)庫基礎(chǔ)的讀者來說是很容易理解的,下面的代碼演示了如何讀取數(shù)據(jù)集:
- var transaction = db.transaction(["test"], "readonly");
- var objectStore = transaction.objectStore("test");
- var cursor = objectStore.openCursor();
- cursor.onsuccess = function(e) {
- var res = e.target.result;
- if(res) {
- console.log("Key", res.key);
- console.dir("Data", res.value);
- res.continue();
- }
- }
其中使用objectStore的openCursor打開游標,并且在onsuccess事件中進行處理,注意使用res獲得了結(jié)果集,然后用res.continue()方法去循環(huán)讀取記錄集。同樣,我們用這個方法去遍歷之前的people存儲集,代碼如下:
- function getPeople(e) {
- var s = "";
- db.transaction(["people"], "readonly").objectStore("people").openCursor().onsuccess = function(e) {
- var cursor = e.target.result;
- if(cursor) {
- s += "
Key "+cursor.key+"
";
- for(var field in cursor.value) {
- s+= field+"="+cursor.value[field]+"
";- }
- s+="";
- cursor.continue();
- }
- document.querySelector("#status2").innerHTML = s;
- }
- }
可以在test9.html中看到完整的代碼,我們可以不斷增加人員信息,然后點獲取所有信息,如下圖運行效果:
IndexedDB中的索引
在本教程的***部分,講解IndexedDB中最重要的部分就是索引了。首先是如何創(chuàng)建索引,這需要在upgrade事件中進行,下面是方法:
- var objectStore = thisDb.createObjectStore("people",
- { autoIncrement:true });
- objectStore.createIndex("name","name", {unique:false});
- objectStore.createIndex("email","email", {unique:true});
其中,在objectStore.createIndex("name","name", {unique:false});
中,***個參數(shù)就是索引的名稱,第二個參數(shù)就是列。并且使用unique指定某個列是否唯一,這里只是認為email是唯一的,name是不唯一的。
那么如何使用索引呢?一旦我們在事務(wù)中獲得對象存儲,則可以通過索引去獲得數(shù)據(jù),一個例子如下:
- var transaction = db.transaction(["people"],"readonly");
- var store = transaction.objectStore("people");
- var index = store.index("name");
- var request = index.get(name);
接下來,可以在onsuccess事件中進行編碼處理,參考test10.html,代碼如下:
- request.onsuccess = function(e) {
- var result = e.target.result;
- if(result) {
- var s = "
Name "+name+"
";
- for(var field in result) {
- s+= field+"="+result[field]+"
";- }
- document.querySelector("#status").innerHTML = s;
- } else {
- document.querySelector("#status").innerHTML = "
No match
";- }
- }
接下來看下另外一個基于索引的Range的用法,通過Range,可以獲得某個數(shù)據(jù)范圍之間的數(shù)據(jù),比如字母a到字母c之間的所有name,同時還可以設(shè)置是否包含或者不包含邊界(如是否包含字母a),并且可以對范圍之間的數(shù)據(jù)進行排序,來看例子:
- //大于39
- var oldRange = IDBKeyRange.lowerBound(39);
- //大于40
- var oldRange2 = IDBKeyRange.lowerBound(40,true);
- //小于39
- var youngRange = IDBKeyRange.upperBound(40);
- //小于39
- var youngRange2 = IDBKeyRange.upperBound(39,true);
- //20到40之間
- var okRange = IDBKeyRange.bound(20,40)
這里使用的是一個頂層的對象名為IDBKeyRange。其中l(wèi)owerBound方法指定返回的數(shù)據(jù)是大于指定的參數(shù)的,upperBound則相反,bound則返回指定范圍之間的數(shù)據(jù)。下面給出一個檢索的例子,其中在頁面表單中可以輸入搜索名稱的開始和結(jié)束范圍,如下:
- Starting with:
- Ending with:
其事件代碼為:
- function getPeople(e) {
- var name = document.querySelector("#nameSearch").value;
- var endname = document.querySelector("#nameSearchEnd").value;
- if(name == "" && endname == "") return;
- var transaction = db.transaction(["people"],"readonly");
- var store = transaction.objectStore("people");
- var index = store.index("name");
- if(name != "" && endname != "") {
- range = IDBKeyRange.bound(name, endname);
- } else if(name == "") {
- range = IDBKeyRange.upperBound(endname);
- } else {
- range = IDBKeyRange.lowerBound(name);
- }
- var s = "";
- index.openCursor(range).onsuccess = function(e) {
- var cursor = e.target.result;
- if(cursor) {
- s += "
Key "+cursor.key+"
";
- for(var field in cursor.value) {
- s+= field+"="+cursor.value[field]+"
";- }
- s+="";
- cursor.continue();
- }
- document.querySelector("#status").innerHTML = s;
- }
- }
在上面的代碼中,我們首先判斷用戶到底在哪個文本框中輸入了檢索條件,然后通過條件判斷組合成了range,然后將range傳遞給打開游標的方法index.openCursor(range)就可以了,IndexedDB則會自動檢索出符合條件的記錄,例子的代碼在test11.html。
在接下來的第二部分的教程中,將探討包括更新、刪除和數(shù)組方面的操作,敬請期待。
本文代碼下載參見:https://github.com/tutsplus/working-with-indexeddb
標題名稱:IndexedDB入門導學
分享地址:http://m.fisionsoft.com.cn/article/cosgdhd.html


咨詢
建站咨詢
