本章內(nèi)容:進(jìn)行離線檢測、使用離線緩存似袁、在瀏覽器中保存數(shù)據(jù)
‘
支持離線 Web 應(yīng)用開發(fā)時 HTML5 的另一個重點存璃。HTML5 把離線應(yīng)用作為重點,主要是基于開發(fā)人員的心愿阎曹。
開發(fā)離線Web應(yīng)用程序伪阶。
- 首先要確保應(yīng)用知道設(shè)備是否能上網(wǎng),以便下一步執(zhí)行正確的操作处嫌。
- 然后栅贴,應(yīng)用還必須能訪問一定的資源(圖像、JavaScript熏迹、CSS 等)檐薯,只有這樣才能正常工作。
- 最后,必須有一塊本地空間用于保存數(shù)據(jù)坛缕,無論能否上網(wǎng)都不妨礙讀寫墓猎。
一、離線檢測
開發(fā)離線應(yīng)用的第一步是要知道設(shè)備是在線還是離線赚楚,HTML5 為此定義了一個 navigator.onLine 屬性毙沾,這個屬性值為 true 表示設(shè)備能上網(wǎng),值為 false 表示設(shè)備離線宠页。
navigator.onLine 在不同瀏覽器間還有些小差距
- IE6+左胞、Safari5+、能夠正確檢測到網(wǎng)絡(luò)已斷開举户,并將 navigator.onLine 的值轉(zhuǎn)換為 false
- Firefox 3+ 和 Opera 10.6+支持 navigator.onLine 屬性烤宙,但你必須手工選中菜單項“文件-> Web開發(fā)人員(設(shè)置) -> 脫機(jī)工作” 才能讓瀏覽器正常工作
以下是檢測該屬性狀態(tài)的示例:
if (navigator.onLine) { // 正常工作
// todo
} else { // 離線狀態(tài)
//todo
}
為了更好地確定網(wǎng)絡(luò)是否可用,HTML5 還定義了兩個事件:online 和 offline敛摘。當(dāng)網(wǎng)絡(luò)從離線變?yōu)樵诰€或者從在線變?yōu)殡x線時门烂,分別觸發(fā)這兩個事件。這兩個事件在 window 對象上觸發(fā)兄淫。
window.ononline = function() {
// todo
}
window.onoffline = function() {
// todo
}
在頁面加載后屯远,最好先通過 navigator.onLine 取得初始的狀態(tài)。然后捕虽,就是通過上述兩個事件來確定網(wǎng)絡(luò)連接狀態(tài)是否變化慨丐。當(dāng)上述事件觸發(fā)時,navigator.onLine 屬性的值也會改變泄私,不過必須手工輪詢這個屬性才能檢測到網(wǎng)絡(luò)狀態(tài)的變化房揭。
二、應(yīng)用緩存
HTML5 的應(yīng)用緩存(application cache)晌端,或者簡稱為 appcache捅暴,是專門為開發(fā)離線 Web 應(yīng)用而設(shè)計的。Appcache 就是從瀏覽器的緩存中分出來的一塊緩存區(qū)咧纠。想要在這個緩存中保存數(shù)據(jù)蓬痒,可以使用一個 描述文件(manifest file),列出要下載和緩存的資源漆羔。
下面是一個簡單的描述文件示例梧奢。
CACHE MANIFEST
# Comment
file.js
file.css
在最簡單的情況下,描述文件中列出的都是需要下載的資源演痒,以備離線時使用
要將描述文件與頁面關(guān)聯(lián)起來亲轨,可以在<html> 中的 manifest 屬性中指定這個文件的路徑,例如:
<html manifest="/offline,manifest">
這個文件的 MIME 類型必須是 text/cache-manifest
同時有相應(yīng)的 JavaScript API 讓你知道它都在做什么鸟顺。這個 API 的核心是 applicationCache 對象惦蚊,這個對象有一個 status 屬性
屬性的值是常量,表示應(yīng)用緩存的如下當(dāng)前狀態(tài):
- 0:無緩存,即沒有與頁面相關(guān)的應(yīng)用緩存蹦锋。
- 1:閑置曾撤,即應(yīng)用在下載描述文件并檢查更新。
- 2:檢查中晕粪,即正在下載描述文件并檢查更新。
- 3:下載中渐裸,即應(yīng)用緩存正在下載描述文件中指定的資源巫湘。
- 4:更新完成,即應(yīng)用緩存已經(jīng)更新了資源昏鹃,而且所有資源都已下載完畢尚氛,可以通過 swapCache() 來使用了。
- 5:廢棄洞渤,即應(yīng)用緩存的描述文件已經(jīng)不存在了阅嘶,因此頁面無法在訪問應(yīng)用緩存。
應(yīng)用緩存還有很多相關(guān)的事件载迄,表示其狀態(tài)的改變讯柔。以下是這些事件。
- checking:在瀏覽器為應(yīng)用緩存查找更新時觸發(fā)护昧。
- error:在檢測更新或下載資源期間發(fā)生錯誤時觸發(fā)
- noupdate:在檢查描述文件發(fā)現(xiàn)文件無變化時觸發(fā)
- downloading:在開始下載應(yīng)用緩存資源時觸發(fā)
- progress:在文件下載應(yīng)用緩存的過程中持續(xù)不斷地觸發(fā)
- updateready:在頁面新的應(yīng)用緩存下載完畢且可以通過 swapCache() 使用時觸發(fā)魂迄。
- cached:在應(yīng)用緩存完整可用時觸發(fā)
一般來講,這些事件會隨著頁面加載按上述順序依次觸發(fā)惋耙。不過捣炬,通常調(diào)用 update() 方法也可以手工干預(yù),讓應(yīng)用緩存為檢查更新而觸發(fā)上述事件绽榛。
applicationCache.update()
update() 一經(jīng)調(diào)用湿酸,應(yīng)用緩存就會去檢查描述文件是否更新(觸發(fā) checking 事件),然后就像頁面剛加載一樣灭美,繼續(xù)執(zhí)行后續(xù)操作推溃。如果觸發(fā)了 cached 事件,就說明應(yīng)用緩存已經(jīng)準(zhǔn)備就緒冲粤,不會再發(fā)生其他操作了美莫。如果觸發(fā)了 updateready 事件,則說明新版本的應(yīng)用緩存已經(jīng)可用梯捕,而此時你需要調(diào)用 swapCache() 來啟用新應(yīng)用緩存
applicationCache.onupdateready = function() {
applicationCache.swapCache()
}
三厢呵、數(shù)據(jù)存儲
隨著 Web 應(yīng)用程序的出現(xiàn),也產(chǎn)生了對于能夠直接在客戶端上存儲用戶信息能力的要求傀顾。這個問題的第一個方案是以 cookie 的形式出現(xiàn)的襟铭,cookie是原來的網(wǎng)景公司創(chuàng)造的。一份題為“Persistent Client State:HTTP Cookies”(持久客戶端狀態(tài):HTTP Cookies)的標(biāo)準(zhǔn)中對 cookie 機(jī)制進(jìn)行了闡述。今天寒砖,cookie 只是在客戶端存儲數(shù)據(jù)的其中一種選項赐劣。
3.1、Cookie
HTTP Cookie哩都,通常直接哦叫做 cookie魁兼,最初是在客戶端用于存儲會話信息的。該標(biāo)準(zhǔn)要求服務(wù)器對任意HTTP 請求發(fā)送 Set-Cookie HTTP 頭作為響應(yīng)的一部分漠嵌,其中包含會話信息咐汞。
例如,這種服務(wù)器響應(yīng)的頭可能如下:
HTTP/1.1 200 OK
Content-type: text/html
Set-cookie: name=value
other-header: other-header-value
這個 HTTP 響應(yīng)設(shè)置以 name 為名稱儒鹿、以 value 為值的一個 cookie化撕,名稱和值在傳送時都必須是 URL 編碼的。瀏覽器會存儲這樣的會話信息约炎,并在這之后植阴,通過為每個請求添加 Cookie HTTP 頭將信息發(fā)送回服務(wù)器,
如下所示:
GET /index.html HTTP/1.1
Cookie: name=value
Other-header: other-header-value
發(fā)送回服務(wù)器的額外信息可以用于唯一驗證客戶來自于發(fā)送的哪個請求圾浅。
3.1.1掠手、限制
cookie 在性質(zhì)上時綁定在特定的域名下的,這個限制確保了儲存在 cookie 中的信息只能讓批準(zhǔn)的接受者訪問贱傀,而無法被其他域訪問惨撇。
每個域的 cookie 總數(shù)有限,不過瀏覽器之間各有不同:
- IE6以及更低版本限制每個域名 最多 20個 cookie
- IE7 和之后版本每個域名最多 50個府寒。IE7最初是支持每個域名 最大 20個 cookie魁衙,之后被微軟一個補(bǔ)丁更新了
- Firefox 限制每個域最多 50個cookie
- Opera 限制每個域最多 30個 cookie
- Safari 和 Chrome 對于每個域的 cookie 數(shù)量限制沒有硬性規(guī)定。
當(dāng)超過單個域名限制之后還要再設(shè)置 cookie株搔,瀏覽器就會清除以前設(shè)置的 cookie剖淀。IE 和 Opera 則會刪除最近最少使用的 cookie。Firefox 看上去好像是 隨機(jī)決定要清除哪個 cookie纤房,所以考慮 cookie限制非常重要纵隔,以免出現(xiàn)不可預(yù)期的后果。
瀏覽器中對于 cookie 的尺寸也有限制炮姨。大多數(shù)瀏覽器都有 大約 4096B(加減一)的長度限制捌刮。尺寸限制影響到一個域下面所有的 cookie,如果你嘗試創(chuàng)建超過最大尺寸限制的cookie舒岸,那么該 cookie 會被悄無聲息地丟掉绅作。
3.1.2、cookie 的構(gòu)成
cookie 有瀏覽器保存的以下幾塊信息構(gòu)成
- 名稱:一個唯一確定的cookie名稱蛾派。cookie 名稱是不區(qū)分大小寫的俄认,然而个少,實踐中最好將 cookie 名稱看作是區(qū)分大小寫的,因為某些服務(wù)器會這樣處理cookie眯杏。cookie的名稱必須經(jīng)過 URL 編碼夜焦。
- 值:儲存在 cookie 中的字符串值。值必須被 URL 編碼
- 域:cookie 對于哪個域是有效的岂贩。所有項該域發(fā)送的請求中都會包含這個 cookie 信息茫经。這個值可以包含子域,如果沒有明確設(shè)定萎津,那么這個域會被認(rèn)作來自設(shè)置 cookie的那個域科平。
- 路徑:對于指定域中的那個路徑,應(yīng)該向服務(wù)器發(fā)送 cookie姜性。
- 失效時間:表示 cookie 何時應(yīng)該被刪除的時間戳(GMT日期格式)。默認(rèn)情況下髓考,瀏覽器會話結(jié)束時即將所有 cookie 刪除部念;
- 安全標(biāo)志:指定后,cookie 只有在使用 SSL 連接到時候才發(fā)送到服務(wù)器氨菇。
每一段信息都作為 Set-Cookie 頭的一部分儡炼,使用分號加空格分隔 每一段。如下所示:
HTTP/1.1 200 OK
Content-type:text/html
Set-Cookie:name=value; expires=Mon, 22-Jan-07 07:10:24 GMT; domain=.wrox.com
Other-header: other-header-value
該頭部信息指定了一個叫做 name 的 cookie查蓉,它會在格林威治事件 2007年 1月 22日 7:10:24失效乌询,同時對于 www.wrox.com 和 wrox.com 的任何子域(如 p2p.wrox.com)都有效
secure 標(biāo)志是 cookie 中唯一一個非名值對兒的部分,直接包含一個 secure 單詞豌研。
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name=value; domain=.wrox.com; path=/; secure
Other-header: other-header-value
因為設(shè)置了 secure 標(biāo)志妹田,這個 cookie 只能通過 SSL 連接才能傳輸。
域鹃共、路徑鬼佣、失效時間、secure 標(biāo)志都是服務(wù)器給瀏覽器的指示霜浴,以指定何時應(yīng)該發(fā)送 cookie晶衷。這些參數(shù)并不會作為發(fā)送服務(wù)器的 cookie 信息的一部分,只有明值對兒才會被發(fā)送阴孟。
3.1.3晌纫、JavaScript 中的 cookie
在 JavaScript 中處理 cookie 有些復(fù)雜,即 BOM的 document.cookie屬性永丝,返回當(dāng)前頁面可用的 所有 cookie 的字符串锹漱,一系列由分號隔開的名值對兒。
name1=value1;name2=value2;name3=value3
所有名字和值都是經(jīng)過URL 編碼的类溢,所以必須使用 decodeURIComponent() 來解碼
當(dāng)用于設(shè)置值的時候凌蔬,document.cookie 屬性可以設(shè)置為一個新的 cookie 字符串露懒。這個cookie 字符串會被添加到 cookie 集合中。
設(shè)置 cookie 的格式如下砂心,和 Set-Cookie 頭中使用的格式一樣懈词。
name=value; expires=expiration_time; path=domain_path; domain=domain_name; secure
這些參數(shù)中,只有 cookie 的名字和值是必需的
document.cookie = 'name=Lee'
最好每次設(shè)置 cookie 時都想下面這個例子中一樣使用 encodeURIComponent()
document.cookie = encodeURIComponent('name') + '=' + encodeURIComponent('Lee');
?
?
由于 JavaScript 中讀寫 cookie 不是非常直觀辩诞,常常需要寫一些函數(shù)來簡化 cookie 的功能坎弯。基本 cookie 操作有三種:讀取译暂、寫入和刪除抠忘。
var CookieUtil = {
get: function(name) {
var cookieName = encodeURIComponent(name) + '=',
cookieStart = document.cookie.indexOf(cookieName), // 查找 查詢鍵的位置
cookieValue = null
if (cookieStart > -1) { // 存在對應(yīng) 鍵
var cookieEnd = document.cookie.indexOf(';', cookieStart) // 查找對應(yīng)鍵 的值結(jié)束位置
if (cookieEnd == -1) { // 屬于 最后一個鍵值對
cookieEnd = document.cookie.length
}
cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd)) // 返回指定部分的字符串
}
return cookieValue
},
set: function(name, value, expires, path, domain, secure) {
var cookieText = encodeURIComponent(name) + '=' + encodeURIComponent(value)
if (expires instanceof Date) {
cookieText += '; expires=' + expires.toGMTString()
}
if (path) {
cookieText += '; path=' + path
}
if (domain) {
cookieText += '; domain' + domain
}
if (secure) {
cookieText += '; secure'
}
document.cookie = cookieText
},
unset: function(name, path, doamin, secure) {
this.set(name, '', new Date(0), path, domain, secure) //設(shè)置過期時間
}
}
可以像下面這樣使用上述方法
// 設(shè)置 cookie
CookieUtil.set('name', 'Nicholas')
CookieUtil.set('book', 'Professional JavaScript')
// 讀取 cookie 的值
console.log(CookieUtil.get('name')) //Nicholas
console.log(CookieUtil.get('book')) // Professional JavaScript
// 刪除 cookie
CookieUtil.unset('name')
CookieUtil.unset('book')
// 設(shè)置 cookie,包括它的路徑外永、域崎脉、失效日期
CookieUtil.set('name', 'Nicholas', '/books/projs/', 'www.wrox.com', new Date('January 1. 2010'))
// 刪除剛剛設(shè)置的 cookie
CookieUtil.unset('name', '/books/projs', 'www.wrox.com')
// 設(shè)置安全的 cookie
CookieUtil.set('name', 'Nicholas', null, null, null, true)
3.1.4、子 cookie
為了繞開瀏覽器的單域名下的 cookie 數(shù)限制伯顶,一些開發(fā)人員使用了 一種稱為 子 cookie(subcookie)的概念囚灼。子 cookie 是存放在單個 cookie中 的更小段的數(shù)據(jù)。
也就是使用 cookie 值來存儲多個名稱值對兒祭衩。子 cookie最常見的格式如下:
name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5
子 cookie 一般也以查詢字符串的格式進(jìn)行格式化灶体。然后這些值可以使用 單個 cookie 進(jìn)行存儲和訪問,而非對每個名稱-值對兒使用不同的 cookie 存儲掐暮。最后網(wǎng)站或者 Web 應(yīng)用程序可以達(dá)到單域名 cookie 上限也可以存儲更加結(jié)構(gòu)化的數(shù)據(jù)蝎抽。
?
為了更好地操作子 cookie,必須建立一系列新方法路克。
var SubCookieUtil = {
get: function(name, subName) {
var subCookies = this.getAll(name) // 獲取子 cookie 對象
if (subCookies) {
return subCookies[subName]
} else {
return null
}
},
getAll: function(name) {
var cookieName = encodeURIComponent(name) + '=',
cookieStart = document.cookie.indexOf(cookieName),
cookieValue = null,
cookieEnd,
subCookies,
i,
len,
parts,
result = {}
if (cookieStart > -1) { // 存在 子 cookie
cookieEnd = document.cookie.indexOf(';', cookieStart) // 子 cookie 結(jié)束的位置
if (cookieEnd == -1) { // 父cookie 是最后一對鍵值
cookieEnd = document.cookie.length // 子 cookie 結(jié)束的位置即為 cookie的長度
}
cookieValue = document.cookie.substring(cookieStart + cookieName.length, cookieEnd)
if (cookieValue.length > 0) { // 反序列化
subCookies = cookieValue.split('&')
for (i = 0, len = subCookies.length; i < len; i++) {
parts = subCookies[i].split('=')
result[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1])
}
return result
}
return null
}
}
}
可以像下面這樣使用 上述方法:
// 假設(shè) cookie 為以下值
document.cookie = 'data=name=Nicholas&book=Professional%20JavaScript'
// 取得全部子cookie
var data = SubCookieUtil.getAll('data')
console.log(data.name) // Nicholas
console.log(data.book) // Professional JavaScript
// 逐個獲取 子 cookie
console.log(SubCookieUtil.get('data', 'name')) // Nicholas
console.log(SubCookieUtil.get('data', 'book')) // Professional JavaScript
要設(shè)置 子 cookie樟结,也有兩種方法:set() 和 setAll()。
以下代碼展示了他們的構(gòu)造
var SubCookieUtil = {
set: function(name, subName, value, expires, path, domain, secure) {
var subcookies = this.getAll(name) || {} // 獲取 cookie 鍵值對象
subcookies[subName] = value // 添加
this.setAll(name, subcookies, expires, path, domain, secure) // 設(shè)置
},
setAll: function(name, subcookies, expires, path, domain, secure) {
var cookieText = encodeURIComponent(name) + '=',
subcookieParts = new Array(),
subName
for (subName in subcookies) { // 遍歷 子 cookie 對象
if (subName.length > 0 && subcookies.hasOwnProperty(subName)) {
// 以鍵值對字符串的形式 保存在數(shù)組中
subcookieParts.push(encodeURIComponent(subName) +'='+ encodeURIComponent(subcookies[subName]))
}
}
if (subcookieParts.length > 0) { // 以 & 為界定符 將字符數(shù)組轉(zhuǎn)換為字符串精算。
cookieText += subcookieParts.join('&')
if (expires instanceof Date) cookieText += '; expires=' + expires.toGMTString()
if (path) cookieText += '; path=' + path
if (domain) cookieText += '; domain=' + domain
if (secure) cookieText += '; secure'
} else {
cookieText += '; expires=' + (new Date(0)).toGMTString()
}
document.cookie = cookieText
}
// ...省略了更多代碼
}
可以按照以下方式 來使用 set() 和 setAll() 方法
// 假設(shè) cookie 為以下值
document.cookie = 'data=name=Nicholas&book=Professional%20JavaScript'
// 設(shè)置兩個 cookie
SubCookieUtil.set('data', 'name', 'Nicholas')
SubCookieUtil.set('data', 'book', 'Professional JavaScript')
// 設(shè)置全部子 cookie 和失效日期
SubCookieUtil.setAll('data', {name: 'Nicholas', book: 'Professional JavaScript'}, new Date('January 1, 2020'))
// 修改名字的值狭吼,并修改 cookie的失效日期
SubCookieUtil.set('data', 'name', 'Michael', new Date('February 1, 2020'))
子 cookie 的最后一組方法是用于刪除 子 cookie 的。普通 cookie 可以將失效時間設(shè)置為過去的時間的方法來刪除殖妇,但是子 cookie 不能這樣做刁笙。首先必須獲取包含在 某個 cookie 中的所有子 cookie,然后僅刪除需要刪除的那個子 cookie谦趣,然后再將余下的子 cookie的值 保存為 cookie的值疲吸。
var SubCookieUtil = {
// ...省略了更多代碼
unset: function(name, subName, path, domain, secure) {
var subCookies = this.getAll(name) // 獲取 子 cookie
if (subCookies) {
delete subCookies[subName] // 刪除
this.setAll(name, subCookies, null, path, domain, secure) // 保存
}
},
unsetAll: function(name, path, domain, secure) { // 設(shè)置過期時間
this.setAll(name, null, new Date(0), path, domain, secure)
}
}
這兩個方法可以像下面這樣使用
// 僅刪除名為 name 的子 cookie
SubCoolieUtil.unset('data', 'name')
// 刪除整個 cookie
SubCookieUtil.unsetAll('data')
?
?
如果你擔(dān)心開發(fā)中可能會達(dá)到單域名的 cookie 上限,那么子 cookie 可是一個非常有吸引力的備選方案前鹅。不過摘悴,你需要更加密切關(guān)注 cookie 的長度,以防超過單個 cookie 的長度限制舰绘。
3.1.5蹂喻、關(guān)于 cookie 的思考
由于 所有的 cookie 都會有瀏覽器作為 請求頭發(fā)送葱椭,所以在 cookie 中存儲大量信息會影響到特定域的 請求性能。cookie 的性質(zhì)和它的局限使得其并不能作為存儲大量信息的理想手段口四。
3.2孵运、IE 用戶數(shù)據(jù)
在 IE5.0 中,微軟通過一個自定義行為引入了持久化用戶數(shù)據(jù)的概念蔓彩。用戶數(shù)據(jù)允許每個文檔最多 128KB 數(shù)據(jù)治笨,每個域名最多 1MB數(shù)據(jù)。
要使用持久化用戶數(shù)據(jù)赤嚼,首先必須如下所示旷赖,使用 CSS 在某個元素上指定 userData 行為:
<div style="behavior: url(#default#userData)" id="dataStore"></div>
?
一旦元素使用了 userData 行為,那么就可以使用 setAttribute() 方法在上面保存數(shù)據(jù)了更卒。為了將數(shù)據(jù)提交到瀏覽器緩存中等孵,還必須調(diào)用 save() 方法并告訴它要保存到的數(shù)據(jù)空間的名字。數(shù)據(jù)空間名字可以完全任意蹂空,僅用于區(qū)分不同的數(shù)據(jù)集流济。
var dataStore = document.getElementById('dataStore')
dataStore.setAttribute('name', 'Nicholas')
dataStore.setAttribute('book', 'Professional JavaScript')
dataStore.save('BookInfo')
?
在下一次頁面載入之后,可以使用 load() 方法指定 同樣的數(shù)據(jù)空間名稱來獲取數(shù)據(jù)
dataStore.load('BookInfo')
console.log(dataStore.getAttribute('name')) // Nicholas
console.log(dataStore.getAttribute('book')) // Prefessional JavaScript
你可以通過 removeAttribute() 方法明確指定要刪除某些元素數(shù)據(jù)腌闯,只要指定屬性名稱。
刪除之后雕憔,必須像下面這樣再次調(diào)用 save() 來提交更改
dataStore.removeAttribute('name')
dataStore.save('BookInfo')
對 IE 用戶數(shù)據(jù)的訪問限制和對 cookie 的限制類似姿骏。要訪問某個數(shù)據(jù)空間,腳本運行的頁面必將來自用一個域名斤彼,在同一個路徑下分瘦,并使用與進(jìn)行存儲的腳本同樣的協(xié)議。和 cookie 不同的是:
- 你無法將 用戶數(shù)據(jù)訪問限制擴(kuò)展到更多的客戶琉苇。
- 用戶數(shù)據(jù)默認(rèn)是可以跨越會話持久存在的嘲玫,同時也不會過期; 數(shù)據(jù)需要通過 removeAttribute() 方法專門進(jìn)行 刪除以釋放空間并扇。
3.3去团、Web 存儲機(jī)制
Web Storage 最早是在 Web 超文本應(yīng)用技術(shù)工作組(WHAT-WG)的Web 應(yīng)用 1.0 規(guī)范中描述的。這個規(guī)范的最初的工作最終成為了 HTML5 的一部分穷蛹。Web Storage 的目的是克服由 cookie 帶來的一些限制土陪,當(dāng)數(shù)據(jù)需要被嚴(yán)格控制在客戶端上時,無須持續(xù)地將數(shù)據(jù)發(fā)回服務(wù)器肴熏。
Web Storage 的兩個主要目標(biāo)是:
- 提供一種在 cookie 之外存儲會話數(shù)據(jù)的途徑鬼雀;
- 提供一直存儲大量可以跨會話存在的數(shù)據(jù)的機(jī)制
最初 的 Web Storage 規(guī)范包含了兩種對象的定義:sessionStorage 和 globalStorage。這兩個對象再支持的瀏覽器中都是以 window 對象屬性的形式存在的蛙吏。
3.3.1源哩、Storage 類型
Storage 類型提供最大的存儲空間(因瀏覽器而異)來存儲名值對兒鞋吉。Storage 的實例與其他對象類似,有如下方法励烦。
- clear():刪除所有值谓着;
- getItem(name):根據(jù)指定的名字 那么獲取對應(yīng)的值。
- key(index):獲得 index 位置處的值的名字
- removeItem(name):刪除有 name 指定的明值對兒崩侠。
- setItem(name, value):為指定的 name 設(shè)置一個對應(yīng)的值漆魔。
其中,getItem()却音、removeItem()改抡、setItem() 可以直接調(diào)用,也可以通過 Storage對象訪問系瓢。不過阿纤,建議使用方法而不是屬性來訪問數(shù)據(jù),以免某個鍵會以外重寫該對象上已經(jīng)存在的成員夷陋。
還可以使用 length 屬性來判斷有多少名值對兒存放在 Storage 對象中欠拾。
3.3.2、sessionStorage 對象
sessionStorage 對象存儲特定于某個會話的數(shù)據(jù)骗绕,也就是該數(shù)據(jù)只保持到瀏覽器關(guān)閉藐窄。存儲在 sessionStorage 中的數(shù)據(jù)可以跨越頁面刷新而存在,同時如果瀏覽器支持酬土,瀏覽器奔潰并重啟之后依然可用
sessionStorage 對象其實是 Storage 的一個實例谜喊,所以可以使用 setItem() 或者直接設(shè)置新的屬性來存儲數(shù)據(jù)业舍。下面是這兩種方法的例子:
// 使用方法存儲數(shù)據(jù)
sessionStorage.setItem('name', 'Nicholas')
// 使用屬性存儲數(shù)據(jù)
sessionStorage.book = 'Professional JavaScript'
不同瀏覽器寫入數(shù)據(jù)方式略有不同邪媳。Firefox 和 WebKit 實現(xiàn)了同步寫入令蛉,而IE 的實現(xiàn)則是異步寫入數(shù)據(jù),對于少量數(shù)據(jù)而言屈呕,這個差異是可以忽略的
sessionStorage 中有數(shù)據(jù)時微宝,可以使用 getItem() 或者通過直接訪問屬性來獲取
數(shù)據(jù)
// 使用方法讀取數(shù)據(jù)
console.log(sessionStorage.getItem('name'))
// 使用屬性讀取數(shù)據(jù)
console.log(sessionStorage.book)
還可以通過結(jié)合 length 屬性 和 key() 方法來迭代 sessionStorage 中的值
for (var i = 0, len = sessionStorage.length; i < len; i++) {
var key = sessionStorage.key(i)
var value = sessionStorage.getItem(key)
console.log(key + ': ' + value)
}
還可以使用 for-in 循環(huán)來迭代 sessionStorage 中的值
for (var key in sessionStorage) {
var value = sessionStorage.getItem(key)
console.log(key + ': ' + value)
}
?
?
要從 sessionStorage 中刪除數(shù)據(jù),可以使用 delete 操作符刪除對象屬性虎眨,也可以調(diào)用 removeItem() 方法蟋软。
// 使用 delete 刪除一個值
delete sessionStorage.name
// 使用方法刪除一個值
sessionStorage.removeItem('book')
sessionStorage 對象應(yīng)該主要用于僅針對會話的小段數(shù)據(jù)的存儲。如果需要跨越會話存儲數(shù)據(jù)嗽桩,那么 globalStorage 或者 localStorage 更為合適
3.3.3钟鸵、globalStorage 對象
要使用 globalStorage,首先要指定 哪些域可以訪問該數(shù)據(jù)涤躲」姿#可以通過方括號標(biāo)記使用屬性來實現(xiàn),
// 保存數(shù)據(jù)
globalStorage['wrox.com'].name = 'Nicholas'
// 獲取數(shù)據(jù)
var name = globalStorage['wrox.com'].name
globalStorage 對象不是 Storage 的實例种樱,而具體的 globalStorage['wrox.com'] 才是蒙袍。這個存儲空間對于 wrox.com 及其所有子域都是可以訪問的俊卤。可以像下面這樣指定子域名
// 保存數(shù)據(jù)
globalStorage['www.wrox.com'].name = 'Nicholas'
// 獲取數(shù)據(jù)
var name = globalStorage['www.wrox.com'].name
這里所指定的存儲空間只能來自 www.wrox.com 的頁面訪問害幅,其他子域名都不行消恍。
某些瀏覽器允許更加寬泛的訪問限制,比如只根據(jù)頂級域名進(jìn)行限制或者允許全局訪問以现。
// 存儲數(shù)據(jù)狠怨,任何人都可以訪問——不建議這樣做
globalStorage[''].name = 'Nicholas'
// 存儲數(shù)據(jù),可以讓任何以 .net 結(jié)尾的域名訪問 —— 不要這樣做邑遏!
globalStorage['net'].name = 'Nicholas'
雖然這些也支持佣赖,但是還是要避免使用這種可寬泛訪問的數(shù)據(jù)存儲,以防止出現(xiàn)潛在的安全問題记盒。
對 globalStorage 空間的訪問憎蛤,是依據(jù)發(fā)起請求的頁面的域名、協(xié)議纪吮、端口來限制的俩檬。這類似于 Ajax 的同源策略。
globalStorage 的每個屬性都是 Storage 的實例碾盟。因此棚辽,可以像如下代碼中這樣使用。
globalStorage['www.wrox.com'].name = 'Nicholas'
globalStorage['www.wrox.com'].book= 'Professional Javascript'
globalStorage['www.wrox.com'].removeItem('name')
console.log(globalStorage['www.wrox.com'].getItem('book'))
如果你事先不能確定域名冰肴,那么使用 location.host 作為屬性名比較安全屈藐。
globalStorage[location.host].name = 'Nicholas'
console.log(globalStorage[location.host].getItem('book'))
如果不使用 removeItem() 或者 delete 刪除,或者用戶未清除瀏覽器緩存嚼沿,存儲在 globalStorage 屬性中的數(shù)據(jù)會 一直保留在磁盤上。這讓 globalStorage 非常適合在客戶端存儲 文檔或長期保存用戶偏好設(shè)置瓷患。
3.3.4骡尽、localStorage 對象
localStorage 對象在 修訂過的 HTML5 規(guī)范中作為持久保存客戶端數(shù)據(jù)的方案取代了 globalStorage。
要訪問一個 localStorage對象擅编,頁面必須來自同一個域名(子域名無效)攀细,使用同一種協(xié)議,在同一個端口上爱态。這相當(dāng)于 globalStorage[location.host]
由于 localStorage 是 Storage 的實例谭贪,所以可以像使用 sessionStorage 一樣來使用它。
// 使用 方法存儲數(shù)據(jù)
localStorage.setItem('name', 'Nicholas')
// 使用 屬性存儲數(shù)據(jù)
localStorage.book = 'Professional JavaScript'
// 使用方法讀取數(shù)據(jù)
console.log(localStorage.getItem('name'))
// 使用屬性讀取數(shù)據(jù)
console.log(localStorage.book)
存儲在 localStorage 中的數(shù)據(jù)和存儲在 globalStorage 中的數(shù)據(jù)一樣锦担,都遵循相同的規(guī)則:數(shù)據(jù)保留到通過 JavaScript 刪除或者是 用戶清楚瀏覽器緩存俭识。
為了兼容只支持 globalStorage 的瀏覽器,可以使用以下函數(shù):
function getLocalStorage() {
if (typeof localStorage == 'object') {
return localStorage
} else if (typeof globalStorage == 'object') {
return globalStorage[location.host]
} else {
throw new Error('Local Storage not available')
}
}
然后洞渔,像下面這樣調(diào)用這個函數(shù)套媚,就可以正常地讀寫數(shù)據(jù)了
var storage = getLocalStorage()
3.3.5缚态、storage 事件
對 Storage 對象進(jìn)行任何修改,都會在文檔上觸發(fā) storage 事件堤瘤。
這個事件的 event 對象有以下屬性:
- domain:發(fā)生變化的存儲空間的域名
- key:設(shè)置或刪除的鍵名
- newValue:如果是設(shè)置值玫芦,則是新值;如果是刪除鍵本辐,則是 null
- oldValue:鍵被更改之前的值
以下代碼展示了如何偵聽 storage 事件
document.onstorage = function(event) {
console.log('Storage changed for ' + event.domain)
}
無論是 sessionStorage桥帆、globalStorage、localStorage 進(jìn)行操作慎皱,都會觸發(fā) storage 事件老虫,但不做區(qū)分。
3.3.6宝冕、限制
Web Storage 也有限制张遭。這些限制因瀏覽器而異。對存儲空間大小的限制都是以每個來源(協(xié)議地梨、域菊卷、端口)為單位的”ζ剩考慮到這個限制洁闰,就要注意分析和控制每個來源中有多少頁面需要保持?jǐn)?shù)據(jù)。
對于 localStorage 而言万细,大多數(shù)桌面瀏覽器會設(shè)置每個來源 5MB的限制扑眉。Chrome 和 Safari 對每個來源的限制時 2.5MB。而 iOS 版 Safari 和 Android 版 Webkit 的限制也是 2.5MB
對 sessionStorage 的限制也是因瀏覽器而異赖钞。有的瀏覽器對 sessionStorage 的大小沒有限制 但Chrome腰素、Safari、iOS 版的Safari 和 Android版 WebKit 都有限制雪营,也都是 2.5MB弓千。IE8+ 和 Opera 對象 sessionStorage 的限制時 5MB。
3.4献起、IndexedDB
Indexed Database API, 是在瀏覽器中保存結(jié)構(gòu)化數(shù)據(jù)的一種數(shù)據(jù)庫洋访。IndexedDB 的思想是創(chuàng)建一套 API,方便保存 和 讀取 JavaScript 對象谴餐,同時還支持查詢及搜索姻政。
indexDB設(shè)計的操作完全是異步進(jìn)行的。差不多每一次 indexedDB 操作岂嗓,都需要你注冊 onerror 或 onsuccess事件處理程序汁展,以確保適當(dāng)處理結(jié)果
在得到完整支持的情況下,IndexDB 將是一個作為API 宿主的全局對象。由于 API 仍然可能有變化善镰,瀏覽器也都使用提供商前綴妹萨,因為這個對象在 IE10 中叫 msIndexedDB,在 Firefox 4中叫 mozIndexedDB炫欺,在 Chrome 中叫 webkitIndexDB乎完。
var indexedDB = window.indexDB || window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB
3.4.1、數(shù)據(jù)庫
IndexedDB 就是一個數(shù)據(jù)庫品洛,最大的特色是使用對象保存數(shù)據(jù)树姨,而不是使用表保持?jǐn)?shù)據(jù)。一個IndexedDB 數(shù)據(jù)庫桥状,就是一組位于相同命名空間下的對象的集合
使用 indexedDB 的第一步是打開它帽揪,即把要打開的數(shù)據(jù)庫名傳給 indexedDB.open()。如果傳入的數(shù)據(jù)庫已經(jīng)存在辅斟,就會發(fā)送一個打開它的請求转晰;如果傳入的數(shù)據(jù)庫還不存在,就會發(fā)送一個創(chuàng)建并打開它的請求士飒。
調(diào)用 indexedDB.open() 會返回一個 IDBRequest 對象查邢,在這個對象上可以添加 onerror 和 onsuccess 事件處理程序。
var indexedDB = window.indexDB || window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB
var request = indexedDB.open('admin')
request.onerror = function(event) {
console.log('Error: ' + event.target.errorCode) // 發(fā)生錯了酵幕,保存一個錯誤碼
}
request.onsuccess = function(event) {
var database = event.target.result // 響應(yīng)成功 數(shù)據(jù)庫實例對象
// console.log(database) // IDBDatabase
}
在這兩個事件處理程序中扰藕, event.target 都指向 request 對象。
以下就是 error 中 errrorCode 可能出現(xiàn)的 錯誤碼(這個錯誤碼適合所有操作):
- IDBDatabaseException.UNKNOWN_ERR(1):意外錯誤芳撒,無法歸類
- IDBDatabaseException.NON_TRANSIENT_ERR(2):操作不合法
- IDBDatabaseException.NOT_FOUND_ERR(3):未發(fā)現(xiàn)要操作的數(shù)據(jù)庫
- IDBDatabaseException.CONSTRAINT_ERR(4):違反了數(shù)據(jù)庫約束
- IDBDatabaseException.DATA_ERR(5):提供給事務(wù)的數(shù)據(jù)不能滿足要求
- IDBDatabaseException.NOT_ALLOWED_ERR(6):操作不合法
- IDBDatabaseException.TRANSACTION_INACTIVE_ERR(7):試圖重用已完成的事務(wù)邓深。
- IDBDatabaseException.ABORT_ERR(8):請求中斷,未成功
- IDBDatabaseException.READ_ONLY_ERR(9):試圖在只讀模式下寫入或修改數(shù)據(jù)
- IDBDatabaseException.TIMEOUT_ERR(10):在有效時間內(nèi)未完成操作
- IDBDatabaseException.QUOTA_ERR(11):磁盤空間不足
?
?
默認(rèn)情況下笔刹,indexedDB 數(shù)據(jù)庫是沒有版本號的芥备,最好一開始就位數(shù)據(jù)庫指定一個版本號。為此舌菜,可以調(diào)用 setVersion() 方法萌壳,傳入以字符串形式表示的版本號。同樣酷师,調(diào)用這個方法也會返回一個請求對象讶凉,需要你再指定時間處理程序染乌。
if (database.version != '1.0') request = database.setVersion('1.0')
3.4.2山孔、對象存儲空間
在建立了與數(shù)據(jù)庫的連接之后下一步就是使用對象存儲空間。如果數(shù)據(jù)庫的版本與你傳入的版本不匹配荷憋,那可能就需要創(chuàng)建一個新的對象存儲空間台颠。在創(chuàng)建對象存儲空間之前,必須要想清楚你想要保存什么類型數(shù)據(jù)。
保存一條記錄的對象應(yīng)該類似如下所示:
var user = {
id: '00,
username: 'momo11123',
password: 'foo'
}
有了這個對象串前,很容易想到 username 屬性可以作為這個對象存儲空間的鍵瘫里。這個 username 必須全局唯一,而且大多數(shù)時候都要通過這個鍵來訪問數(shù)據(jù)荡碾,這一點非常重要谨读,因為在創(chuàng)建對象存儲空間時,必須指定這個一個鍵坛吁。
以下是為了保存上述用戶記錄而創(chuàng)建對象存儲空間的示例:
request.onupgradeneeded = function(event) { // 必須在 upgradeneeded 事件中進(jìn)行創(chuàng)建
var db = event.target.result;
var objectStore = db.createObjectStore("users", { keyPath: "id" });
console.log('數(shù)據(jù)庫版本更改為: ' + db.version);
}
第一次打開成功后或者版本有變化時會觸發(fā)這個事件:一般用于初始化數(shù)據(jù)庫劳殖。其中,第二個參數(shù)中的keyPath 屬性拨脉,就是空間中將有保存的對象的一個屬性哆姻,而這個屬性將作為存儲空間的 鍵來使用
接下來可以使用 add() 或 put() 方法來向其中添加數(shù)據(jù)。這兩個方法都接受一個參數(shù)玫膀,即要保存的對象矛缨。區(qū)別在于,在空間中已經(jīng)包含鍵值相同的對象時帖旨,add() 會返回錯誤箕昭,而 put() 會重寫原有對象。
// users 中保存著一批用戶對象
var users = [
{
id: '001',
username: 'lwx',
password: 'mom1123'
},
{
id: '002',
username: 'smlz',
password: 'momo123'
},
]
var i = 0, len = users.length
while(i < len) {
store.add(users[i++])
}
每次調(diào)用 add() 或 put() 都會創(chuàng)建一個新的針對這個對象存儲空間的更新請求碉就。如果想驗證請求是否完成成功盟广,可以把返回的請求對象保存在一個變量中,然后再指定 onerror 或 onsuccess 事件處理程序
// users(數(shù)組) 中保存著一批 用戶對象
var users = [
{
id: '001',
username: 'lwx',
password: 'mom1123'
},
{
id: '002',
username: 'smlz',
password: 'momo123'
},
]
var i = 0,
request,
requests = [],
len = users.length
while(i < len) {
request = store.add(users[i++])
request.onerror = function() {
// 處理錯誤
}
request.onsuccess = function() {
// 處理成功
}
request.push(request)
}
3.4.3瓮钥、事務(wù)
跨越創(chuàng)建對象存儲空間這一步后筋量,接下來的所有操作都是通過事務(wù)來完成的。在數(shù)據(jù)庫對象上調(diào)用 transaction() 方法可以創(chuàng)建事務(wù)碉熄。任何時候桨武,只要想讀取或修改數(shù)據(jù),都要通過事務(wù)來組織所有操作锈津。在最簡單的情況下呀酸,可以像下面這個創(chuàng)建事務(wù)。
var transaction = db.transaction('users')
如果要訪問多個對象存儲空間琼梆,也可以在第一個參數(shù)的位置上傳入字符串?dāng)?shù)組
var transaction = db.transaction(['users', 'anothersStore'])
這些事務(wù)都是以只讀方式訪問數(shù)據(jù)性誉。要修改訪問方式,必須在創(chuàng)建事務(wù)時傳入第二個參數(shù)茎杂,這個參數(shù)表示訪問模式错览,用 IDBTransaction 接口定義的如下常量表示:
- READ_ONLY(0):表示只讀
- READ_WRITE(1):表示讀寫
- VERSION_CHANGE(2):表示改變
IE 10+ 和 Firefox 4+ 實現(xiàn)的是 IDBTransaction,但在 Chrome 中則叫 webkitIDBTransaction煌往,所以使用下面的代碼可以統(tǒng)一接口:
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction
如此倾哺,就可以更方便的為 transaction() 指定第二個參數(shù)了
var transaction = db.transaction('users', IDBTransaction.READ_WRITE)
取得了事務(wù)的索引后,使用 objectStore() 方法并傳入存儲空間的名稱,就可以訪問特定的存儲空間羞海。然后忌愚,可以像以前一樣使用 add() 和 put() 方法,此外:
- get() 用來獲取值 —— 接受一個對象鍵作為參數(shù)
- delete() 用來刪除對象 —— 接受一個對象鍵作為參數(shù)
- clear() 則可以刪除所有對象
var transaction = db.transaction('users', IDBTransaction.READ_WRITE)
var request = transaction.objectStore('users').get('001')
request.onerror = function(event) {
console.error('Error ' + event.error.message)
}
request.onsuccess = function(event) {
var result = event.target.result
console.log(result.username) // lwx
}
因為 一個事務(wù) 可以完成任何多個請求却邓,所以事務(wù)對象本身也有事件處理程序:onerror 和 oncomplete硕糊。這兩個事件可以提供事務(wù)級的狀態(tài)信息。
transaction.onerror = function(event) {
// 整個事務(wù)都被取消了
}
transaction.oncomplete = function(event) {
// 整個事務(wù)都成功完成了
}
注意:通過 oncomplete 事件的事件對象(event)訪問不到 get() 請求返回的任何數(shù)據(jù)腊徙。必須在 相應(yīng)請求的 onsuccess 事件處理程序中才能訪問到數(shù)據(jù)癌幕。
3.4.4、使用游標(biāo)查詢
使用 事務(wù)可以直接通過已知的鍵檢索單個對象昧穿。而在需要檢索多個對象的情況下勺远,則需要在事務(wù)內(nèi)部創(chuàng)建游標(biāo)。游標(biāo)就是一指向結(jié)果集的指針时鸵。游標(biāo)并不提前收集結(jié)果胶逢。游標(biāo)指針會先指向結(jié)果中的第一項,在接到查找下一項指令是饰潜,才會指向下一項初坠。
在對象存儲空間上調(diào)用 openCursor() 方法可以創(chuàng)建游標(biāo)。與 indexedDB 中的其他操作一樣彭雾,openCursor() 方法返回的是一個請求對象碟刺,因此必須為該對象指定 onsuccess 和 onerror 事件處理程序。
var store = db.transaction('users').objectStore('users')
var request = store.openCursor() // 創(chuàng)建游標(biāo)
request.onsuccess = function(event) {
// 處理成功
}
request.onerror = function(event) {
// 處理失敗
}
在 onsuccess 事件處理程序執(zhí)行時薯酝,可以通過 event.target.result 取得存儲空間中的下一個對象半沽。在結(jié)果集中有下一項時,這個屬性中保存一個 IDBCursor 的實例吴菠,在沒有下一項時者填,這個屬性的值未 null。IDBCursor 的實例有以下幾個屬性做葵。
- direction:數(shù)值占哟,表示游標(biāo)移動的方向。
- 默認(rèn)值為 IDBCursor.NEXT(0)酿矢,表示下一項
- IDBCursor.NEXT_NO_DUPLICATE(1)榨乎,表示下一個不重復(fù)的項
- DBCursor.PREV(2),表示前一項
- IDBCursor.PREV_NO_DUPLICATE 表示前一個不重復(fù)的項瘫筐。
- key:對象的鍵
- value:實際的對象
- primaryKey:游標(biāo)使用的鍵蜜暑。可能是對象鍵严肪,也可能是索引鍵
檢索某一個結(jié)果的信息:
request.onsuccess = function(event) {
var cursor = event.target.result
if (cursor) { // 必須要檢查
console.log('Key:' + cursor.key + ', Value: ' + JSON.stringify(cursor.value))
}
}
?
?
使用 游標(biāo)可以更新個別的記錄史煎,調(diào)用 update() 方法可以用指定的對象更新當(dāng)前游標(biāo)的 value。與其他操作一樣驳糯,調(diào)用 update() 方法也會創(chuàng)建一個 新請求篇梭,因此如果你想知道結(jié)果,就要為它指定 onsuccess 和 onerror 事件處理程序酝枢。
var store = db.transaction('users', 'readwrite').objectStore('users') // 更新需要在讀寫模式下進(jìn)行
var request = store.openCursor() // 創(chuàng)建游標(biāo)
request.onerror = function(event) {
console.error('Error ' + event.error.message)
}
request.onsuccess = function(event) {
var cursor = event.target.result
if (cursor) { // 必須要檢查
if (cursor.key == '001') {
var value = cursor.value
value.password = 'MOMO_)GG'
var updateRequest = cursor.update(value)
updateRequest.onsuccess = function(event) {
console.log('處理成功')
}
updateRequest.onerror = function(event) {
console.error('Error ' + event.taregt.error.message)
}
}
}
}
如果調(diào)用 delete() 方法恬偷,就會刪除相應(yīng)的記錄。與 update() 一樣帘睦,調(diào)用 delete() 也返回一個請求
request.onsuccess = function(event) {
var cursor = event.target.result
console.log(cursor)
if (cursor) { // 必須要檢查
if (cursor.key == '001') {
vardeleteRequest = cursor.delete()
deleteRequest.onsuccess = function(event) {
console.log('處理成功')
}
deleteRequest.onerror = function(event) {
console.error('Error ' + event.taregt.error.message)
}
}
}
}
如果當(dāng)前事務(wù)沒有修改 對象存儲空間的權(quán)限袍患,update() 和 delete() 會拋出錯誤
?
默認(rèn)情況下,每個游標(biāo)只發(fā)送起 一次請求竣付。要想發(fā)起另一次請求诡延,必須調(diào)用下面的一個方法
- continue([key]):移動到結(jié)果集中的下一項。參數(shù) key 是可選的古胆,不指定這個參數(shù)肆良,游標(biāo)移動到下一項;指定這個參數(shù)逸绎,游標(biāo)會移動到指定位置惹恃。
- advance(count):向前移動 count 指定的項數(shù)
著兩個方法都會導(dǎo)致游標(biāo)使用相同的請求,因此同樣的 onsuccess 和 onerror 使勁按處理程序也會得到重用棺牧。例如巫糙,下面的例子遍歷了對象存儲空間中的所有項。
request.onsuccess = function(event) {
var cursor = event.target.result
console.log(cursor)
if (cursor) { // 必須要檢查
cursor.continue() // 移動到下一項
} else {
console.log('Done!')
}
}
調(diào)用 continue() 會觸發(fā)另一次請求颊乘,進(jìn)而再次調(diào)用 onsuccess 事件處理程序参淹。在沒有更多項可以迭代時,將最后一次調(diào)用 onsuccess 事件處理程序乏悄,此時 event.target.result 的值為 null承二。
3.4.5、鍵范圍
鍵范圍(key range) 為適用游標(biāo)增添了一些靈活性纲爸。鍵范圍有 IDBKeyRange 的實例表示亥鸠。
支持標(biāo)準(zhǔn) IDBKeyRange 類型的瀏覽器有 IE10+、Firefox4+识啦,Chrome 中的名字叫 webkitIDBKeyRange负蚊。與使用 IndexedDB 中的其他類型一樣,你最好先聲明一個本地的類型颓哮,同時要考慮到不同瀏覽器中的差異家妆。
var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange
有四種定義鍵范圍的方式:
- 使用 only() 方法,傳入你想要取得的對象的鍵
var onlyRange = IDBKeyRange.only('001')
這個范圍可以保證只取得鍵為 ‘007’ 的對象冕茅。使用這個范圍創(chuàng)建的游標(biāo)與直接訪問存儲空間并調(diào)用 get('001')差不多
- 指定結(jié)果集的下界伤极,lowerBound()蛹找。下界表示游標(biāo)開始的位置。例如哨坪,以下鍵范圍可以保證游標(biāo)從鍵為‘001’的對象開始庸疾,然后繼續(xù)向西移動,直至最后一個對象
// 從鍵為 001 的對象開始当编,然后可以移動到最后
var lowerRange = IDBKeyRange.lowerBound('001')
如果你想忽略鍵為 001 的對象届慈,從它的下一個對象開始,那么可以傳入第二個參數(shù) true:
var lowerRange = IDBKeyRange.lowerBound('001', true)
- 指定結(jié)果集上界忿偷,也就是指定游標(biāo)不能超越哪個鍵金顿。指定上界使用 upperRange() 方法
// 從頭開始,到鍵為 002 的對象為止
var upperRange = IDBKeyRange.upperBound('002')
如果不想包含鍵為指定值對象鲤桥,同樣揍拆,傳入第二個參數(shù) true
var upperRange = IDBKeyRange.upperBound('002', true)
- 同時指定上、下界茶凳,使用 bound() 方法礁凡。這個方法可以接收 4個參數(shù):表示下界的鍵、表示上界的鍵慧妄、可選的表示是否跳過下界的布爾值顷牌、可選的表示是否跳過下界的布爾值和可選的表示是否跳過上界的布爾值。
// 從鍵為 001 的對象開始塞淹,到鍵為007的對象
var boundRange = IDBKeyRange.bound('001', '007')
// 從鍵為 001 的對象的下一個對象開始窟蓝,到鍵為 007 的對象為止
var boundRange = IDBKeyRange.bound('001', '007', true)
// 從鍵為 001 的對象的下一個對象開始,到鍵為 007 的對象的上一個為止
var boundRange = IDBKeyRange.bound('001', '007', true, true)
// 從鍵為 001 的對象開始饱普,到鍵為 007 的對象的上一個對象為止
var boundRange = IDBKeyRange.bound('001', '007', false, true)
?
?
無論如何运挫,在定義鍵范圍之后,把它傳給 openCursor() 方法套耕,就能得到一個符合響應(yīng)約束條件的游標(biāo)谁帕。
var store = db.transaction('users', 'readwrite').objectStore('users') // 更新需要在讀寫模式下進(jìn)行
var range = IDBKeyRange.bound('001', '007')
var request = store.openCursor(range) // 創(chuàng)建游標(biāo)
request.onerror = function(event) {
console.error('Error ' + event.error.message)
}
request.onsuccess = function(event) {
var cursor = event.target.result
if (cursor) { // 必須要檢查
console.log('key: ' + cursor.key + ': ' + JSON.stringify(cursor.value))
cursor.continue() // 移動到下一項
} else {
console.log('done!')
}
}
3.4.6、設(shè)定游標(biāo)方向
實際上冯袍,openCursor() 可以接收兩個參數(shù)
- 第一個參數(shù)是剛剛上面演示的 IDBKeyRange 的實例匈挖。
- 第二個是表示方向的數(shù)值常量。
首先還是本地消除差異:
var IDBCursor = window.IDBCursor || window.webkitIDBCursor
游標(biāo)的默認(rèn)方向值是 IDBCursor.NEXT康愤。如果對象存儲空間中有重復(fù)的項儡循,而你想讓游標(biāo)跳過那些重復(fù)的項,可以為 openCursor 傳入 IDBCursor.NEXT_NO_DUPLICATE 作為第二個參數(shù)
var store = db.transcation('users').objectStore('users')
var request = store.openCursor(null, IDBCursor.NEXT_NO_DUPLICATE)
當(dāng)然征冷,也可以創(chuàng)建一個游標(biāo)择膝,讓他在對象存儲空間中向后移動,即從最后一個對象開始检激,逐個迭代肴捉,直至第一個對象腹侣。此時,要傳入的常量是 IDBCursor.PREV 和 IDBCursor.PREV_NO_DUPLICATE齿穗。
var store = db.transcation('users').objectStore('users')
var request = store.openCursor(null, IDBCursor.PREV)
使用 上述兩種參數(shù) 打開游標(biāo)時傲隶,每次調(diào)用 continue() 或 advance(),都會在存儲空間中向后而不是向前移動游標(biāo)
3.4.7缤灵、索引
對于某些數(shù)據(jù),可能需要為一個對象存儲空間指定多個鍵蓝晒,除主鍵外腮出,還可以創(chuàng)建索引。
首先芝薇,引用對象存儲空間胚嘲,然后調(diào)用 createIndex() 方法
var store = db.transaction('users').objectStore('users'),
index = store.createIndex('username', 'username', {unique: false})
createIndex() 三個參數(shù)分別表示:
- 第一個參數(shù)是索引的名字
- 第二個參數(shù)是索引的屬性的名字
- 第三個參數(shù)是一個包含 unique 屬性的選項(options)對象。這個選項通常都必須指定洛二,因為它表示鍵所在記錄中是否唯一馋劈。
因為 username 有可能重復(fù),所以這里的值為 false
createIndex() 的返回值是 IDBIndex 的實例晾嘶。在對象存儲空間上 調(diào)用 index() 方法也能返回同一個實例妓雾。
var store = db.transaction('users').objectStore('users'),
index = store.index('username') // 獲取索引
在索引上調(diào)用 openCursor() 方法也可以創(chuàng)建新的游標(biāo),除了將來會把索引鍵而非主鍵保存在 event.result.key 屬性中之外垒迂,這個游標(biāo)與在對象存儲空間上調(diào)用 openCursor() 返回的游標(biāo)完全一樣械姻。
var store = db.transaction('users').objectStore('users'),
index = store.index('username'),
request = index.openCursor()
request.onsuccess = function(event) {
// 處理成功
}
在索引上也能創(chuàng)建一個特殊的只返回每條記錄主鍵的游標(biāo),那就要調(diào)用 openKeyCursor() 方法机断。其中 event.result.key 中保存著索引鍵楷拳,而 event.result.value 中保存的則是主鍵
var store = db.transaction('users').objectStore('users'),
index = store.index('username'),
request = index.openCursor()
request.onsuccess = function(event) {
// 處理成功
console.log(event.result)
}
同樣,使用 get() 方法能夠從索引中取得一個對象吏奸,只要傳入相應(yīng)的索引鍵即可欢揖;這個方法也將返回一個請求
var store = db.transaction('users', 'readwrite').objectStore('users'),
index = store.index('id'),
request = index.openKeyCursor('007')
request.onerror = function(event) {
// 處理失敗
}
request.onsuccess = function(event) {
// 處理失敗
}
要根基給定的索引鍵取得主鍵,可以使用 getKey() 方法奋蔚。這個方法也會創(chuàng)建一個新的請求她混,但 event,result.value 等于主鍵的值,而不是包含整個對象
var store = db.transaction('users', 'readwrite').objectStore('users'),
index = store.index('id'),
request = index.getKey('007')
request.onsuccess = function(event) {
// 處理成功
}
任何時候泊碑,通過 IDBIndex 對象的下列屬性都可以取得有關(guān)索引的相關(guān)信息
- name:索引的名字产上。
- keyPath:傳入 createIndex() 中的屬性路徑
- objectStore:索引的對象存儲空間。
- unique:表示索引鍵是否唯一的布爾值
另外蛾狗,通過對象存儲對象的 indexName 屬性可以訪問到位該空間建立的所有索引晋涣。
根據(jù)存儲的對象建立了哪些索引
var store = db.trasaction('users').objectStore('users'),
indexNames = store.indexNames
index,
i = 0,
len = indexNames.length
while(i < len) {
index = store.index(indexNames[i++])
console.log('index Name: ' + index.name + ', keyPath: ' + index.keyPath + ', unique: ' + index.unique)
}
在 對象存儲空間上調(diào)用 deleteIndex() 方法 并傳入索引的名字可以刪除索引。
var store = db.transaction('users').objectStore('users')
store.deleteIndex('username')
3.4.8沉桌、并發(fā)問題
雖然網(wǎng)頁中的 IndexedDB 提供的是異步 API谢鹊,但任然存在并發(fā)操作的問題算吩。
如果瀏覽器的兩個不同的標(biāo)簽打開了同一個頁面,那么一個頁面視圖更新另一個頁面尚未準(zhǔn)備就緒的數(shù)據(jù)庫的問題就有可能發(fā)生佃扼。
把數(shù)據(jù)庫設(shè)置為新版本有可能導(dǎo)致這個問題偎巢。因此,只有當(dāng)瀏覽器中僅有一個標(biāo)簽頁使用數(shù)據(jù)庫的情況下兼耀,調(diào)用 setVersion() 才能完成操作压昼。
剛打開數(shù)據(jù)庫時,要記著指定 onversionchange 事件處理程序瘤运,當(dāng)同一個來源的另一個標(biāo)簽頁調(diào)用 setVersion() 時窍霞,就會執(zhí)行這個回調(diào)函數(shù)。處理這個事件的最佳方式是關(guān)閉數(shù)據(jù)庫拯坟,從而保證版本更新順利完成但金。
var request, database
request = indexedDB.open('admin')
request.onsuccess = function(event) {
database = event.target.result
database.onversionchange = function() {
database.close() // 關(guān)閉數(shù)據(jù)庫
}
}
調(diào)用 setVersion() 時,指定請求的 onblocked 事件處理程序也很重要郁季。在你想要更新數(shù)據(jù)庫的版本但另一個標(biāo)簽頁已經(jīng)打開數(shù)據(jù)庫的情況下冷溃,就會觸發(fā)這個事件處理程序。此時梦裂,最好先通知用戶關(guān)閉其他標(biāo)簽頁似枕,然后再重新調(diào)用 setVersion()。例如:
var request = database.setVersion('2.0')
request.onblocked = function() {
alert('Please c;pse all other tabs any try again.')
}
request.onsuccess = function(event) {
// 處理成功...
}
請記住年柠,其他標(biāo)簽頁中的 onversionchange 事件處理程序也會執(zhí)行
3.4.9菠净、限制
對 IndexedDB 的限制 很多斗魚 對 Web Storage 的類似。
- 首先彪杉,IndexedDB 數(shù)據(jù)庫只能由同源(相同協(xié)議毅往,域名、端口)頁面操作派近,因此不能跨域共享信息攀唯。換句話說,www.wrox.com 與 p2p.wrox.com 的數(shù)據(jù)庫是完全獨立的渴丸。
- 其次侯嘀,每個來源的數(shù)據(jù)庫占用的磁盤空間也有限制。Firefox 4+ 目前的上限是 每個源 50MB谱轨,而 Chrome 的限制時 5MB戒幔。移動設(shè)備上的 Firefox 最多運行保存 5MB,如果超過了這個配額土童,將會請求用戶的許可
Firefox 還有另一個限制诗茎,即不允許本地文件訪問IndexedDB。Chrome 沒有這個限制献汗。
四敢订、小結(jié)
??????離線 Web 應(yīng)用程序和客戶端存儲數(shù)據(jù)的能力對未來的Web 應(yīng)用越來越重要王污。瀏覽器已經(jīng)能夠檢測到用戶是否離線,并處罰 JavaScript 事件以便應(yīng)用做出處理楚午≌哑耄可以指定在應(yīng)用緩存中保存哪些文件以便離線時使用,對于應(yīng)用緩存的狀態(tài)及變化矾柜,也有相應(yīng)的 JavaScript API 可以調(diào)用 檢測阱驾。
客戶端存儲
- cookie 是一小塊可以客戶端設(shè)置 也可以在 服務(wù)器端設(shè)置的信息,每次請求時都會傳送它怪蔑。
- 在JavaScript中 通過 document.cookie 進(jìn)行訪問 cookie
- cookie 的限制使其可以存儲少量數(shù)據(jù)里覆,對于大量數(shù)據(jù)效率很低。
??????Web Storage 定義了兩種用于存儲數(shù)據(jù)的對象:sessionStorage饮睬、localStorage租谈。前者嚴(yán)格用于在一個瀏覽器會話中存儲數(shù)據(jù)篮奄,因為數(shù)據(jù)在瀏覽器關(guān)閉后會立即刪除捆愁;后者用于跨會話持久數(shù)據(jù)并遵循跨域安全策略。
??????IndexDB 是一種類似 SQL 數(shù)據(jù)庫的結(jié)構(gòu)化數(shù)據(jù)存儲機(jī)制窟却。但它的數(shù)據(jù)不是保存在表中昼丑,而是保存在對象存儲空間中。創(chuàng)建對象存儲空間時夸赫,需要定義一個鍵菩帝,然后就可以添加數(shù)據(jù)〔缤龋可以使用游標(biāo)在對象存儲空間中查詢特定的對象呼奢。而索引則是為了提高查詢速度而基于特定的屬性創(chuàng)建的。