所謂Web離線應用,就是在設備不能上網的狀況下仍然能夠運行的應用。開發離線Web應用須要幾個步驟:
(1)確保應用知道設備是否能上網;
(2)應用還必須能訪問必定的資源(圖像、JavaScript、CSS等);
(3)必須有一塊本地空間用於保存數據,不管可否上網都不妨礙讀寫。css
HTML5定義了一個navigator.onLine屬性,這個屬性值爲true表示設備能上網,值爲false表示設備離線。
單獨使用navigator.onLine屬性不能肯定網絡是否連通。即使如此,在請求發生錯誤的狀況下,檢測這個屬性仍然是管用的。html
if(navigator.onLine) { //正常工做 } else { //執行離線狀態時的任務 }
除了navigator.onLine屬性以外,爲了更好地肯定網絡是否可用,HTML5還定義了兩個事件:onLine和offline。當網絡從離線變爲在線或者從在線變爲離線時,分別觸發這兩個事件。這兩個事件在window對象上觸發。瀏覽器
//從離線變爲在線 EventUtil.addHandler(window, "online", function() { alert("Online"); }); //從如今變爲離線 EventUtil.addHandler(window, "offline", function() { alert("Offline"); });
爲了檢測應用是否離線,在網絡加載後,最好先經過navigator.onLine取得初始的狀態。而後,就是經過上述兩個事件來肯定網絡鏈接狀態是否變化。當上述事件觸發時,navigator.onLine屬性的值也會改變。緩存
(1)HTML5的應用緩存,或者簡稱爲appache,是專門爲開發離線Web應用而設計的。Appcache就是從瀏覽器的緩存中分出來的一塊緩存區。要想在這個緩存中保存數據,可以使用一個描述文件(manifest file)列出要下載和緩存的資源。安全
CACHE MANIFEST #Comment file.js file.css
要將描述文件與頁面關聯起來,能夠在<html>中的manifest屬性中指定這個文件的路徑,例如服務器
<html manifest="/offline.manifest">
以上代碼告訴頁面,/offline.manifest中包含着描述文件。cookie
(2)雖然應用緩存的意圖是確保離線時資源可用,但也有相應的JavaScript API讓咱們知道它都在作什麼,這個API的核心是applicationCache對象,這個對象有一個status屬性,屬性的值是常量,表示應用緩存的以下當前狀態。網絡
(3)應用緩存還有不少相關的事件,表示其狀態的改變。如下是這些事件:app
通常來說,這些時間會隨着頁面加載按上述順序依次觸發,不過經過調用update()方法也能夠手工干預,讓應用緩存爲檢查更新而觸發上述事件。dom
applicationCache.update();
update()已經一經調用,應用緩存就會去檢查描述文件是否更新(觸發checking事件),而後就像頁面剛剛加載同樣,繼續執行後續操做。若是觸發了cached事件,就說明應用緩存已經準備就緒,不會再發生其餘操做了。若是觸發了updateready事件,則說明新版本的應用緩存已經可用,而此時你須要調用swapCache()來啓用新應用緩存。
Event.addHandler(applicationCache, "updateready", function() { applicationCache.swapCache(); });
HTTP Cookie,一般直接叫作cookie,最初是在客戶端用於存儲會話信息的。該標準要求服務器對任意HTTP請求發送Set-Cookies HTTP頭做爲響應的一部分,其中包含會話信息。例如,這種服務器響應頭可能以下:
HTTP/1.1 200 OK Content-type: text/html Set-Cookie: name=value Other-header: other-header-value
這個HTTP響應設置以name爲名稱,以value爲值得一個cookie,名稱和值在傳送時都必須是URL編碼的。瀏覽器會存儲這樣的會話信息,並在這以後,經過爲每一個請求添加Cookie HTTP頭將信息發送回服務器。
GET /index.html HTTP/1.1 Cookie: name=value Other-header: other-header-value
發送回服務器的額外信息能夠用於惟一驗證客戶來自於發送的哪一個請求。
一、限制(綁定在特定域名下;數量限制;尺寸限制)
(1)cookie在性質上是綁定在特定的域名下的。當設定了一個cookie後,再給建立它的域名發送請求時,都會包含這個cookie。這個限制確保裏存儲在cookie中的信息只能讓批准的接受者訪問,而沒法被其餘域訪問。
(2)每一個域的cookie總數老是有限的,當超過單個域名限制以後還要再設置cookie,瀏覽器就會清除之前設置的cookie。IE和Opera會刪除最近最少使用過的cookie,騰出空間給新設置的cookie。Firefox看上去好像是隨機決定要清除哪一個cookie,因此考慮cookie限制很是重要,以避免出現不可預期的後果。
(3)瀏覽器中對於cookie的尺寸也有限制。尺寸限制影響到一個域下全部的cookie,而並不是每一個cookie單獨限制。若是你嘗試建立超過最大尺寸限制的cookie,那麼該cookie會被悄無聲息地丟掉。
二、cookie的構成
cookie由瀏覽器保存的一下幾塊信息構成。(名稱、值、域、路徑、失效時間、安全標誌)
每一段信息都做爲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
secure標誌是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
這裏建立了一個對於全部wrox.com的子域和域名下(由path參數指定的)全部頁面都是有效的cookie。由於設置了secure標誌,這個cookie只能經過SSL鏈接才能傳輸。
尤爲要注意,域、路徑、失效時間和secure標誌都是服務器給瀏覽器的指示,以指定什麼時候應該發送cookie。這些參數並不會做爲發送到服務器的cookie信息的一部分,只有名值對兒纔會被髮送。
三、JavaScript中的cookie
JavaScript中處理cookie有些複雜,由於BOM的document.cookie屬性比較獨特,它會由於使用它的不一樣而表現出不一樣的行爲。
當用來獲取屬性值時,document.cookie返回當前頁面可用的(根據cookie的域、路徑、失效時間和安全設置)全部cookie的字符串,一系列由分號隔開的名值對兒。
當用於設置值的時候,document.cookie屬性能夠設置爲一個新的cookie字符串。這個cookie字符串會被解釋並添加到現有的cookie集合中。設置document.cookie並不會覆蓋cookie,除非設置cookie的名稱已經存在。設置cookie的格式以下,和Set-Cookie頭中使用的格式同樣。
name=value; expires=expiration_time; path=domain_name; secure
這些參數中,只有cookie的名字和值是必需的。如:最好每次設置cookie時都像下面這樣使用encodeURI-Component();
document.cookie = encodeURIComponent("name") + "=" + encodeURIComponent("Nicholas");
要給被建立的cookie指定額外的信息,只要將參數追加到該字符串,和Set-Cookie頭中的格式同樣,以下所示:
document.cookie = encodeURIComponent("name") + "=" + encodeURIComponent("Nicholas") + "; domain=.wrox.com; path=/";
因爲JavaScript中讀寫cookie不是很是直觀,經常須要寫一些函數來簡化cookie功能。基本的cookie操做有3種:讀取、寫入和刪除。
全部名字和值都是通過URL編碼的,全部必須使用decodeURIComponent()來解碼
var CookieUtil = { get: function(name){ //查找cookie名加上等於號的位置。若是找到了,那麼使用indexOf()查找該位置以後的第一個分號(表示了該cookie的結束位置)。若是沒有找到分號,則表示該cookie是字符串中的最後一個,則餘下的字符串都是cookie的值。該值使用decodeURIComponent()進行解碼並最後返回。若是沒有發現cookie,則返回null。 var cookieName = encodeURIComponent(name) + "=", cookieStart = document.cookie.indexOf(cookieName), cookieValue = null; if(cookieStart > -1){ var cookieEnd = document.cookie.indexOf(";",cookieStart); 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; }, //沒有刪除已有cookie的直接方法。因此,須要使用相同的路徑、域和安全選項再次設置cookie,並將失效時間設置爲過去的時間。 unset: function (name, path, domain, secure) { this.set(name, "", new Date(0), path, domain, secure); } };
而後就能夠像下面這樣使用上述方法
//設置cookie CookieUtil.set("name", "Nicholas"); CookieUtil.set("book", "Professional JavaScript"); //讀取cookie的值 alert(CookieUtil.get("name")); //"Nicholas" alert(CookieUtil.get("book")); //"professional JavaScript" //刪除cookie CookieUtil.unset("name"); CookieUtil.unset("book");
四、子cookie
爲了繞開瀏覽器的單域名下的cookie數限制,一些開發人員使用了一種稱爲子cookie(subcookie)的概念。子cookie是存放在的那個cookie中的更小段的數據。也就是使用cookie值來存儲多個名稱值對兒。子cookie最多見的格式以下
name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5
子cookie通常也以查詢字符串否定格式進行格式化。而後這些值可使用單個cookie進行存儲和訪問,而非對每一個名稱-值對兒使用不一樣的cookie存儲。最後網站或者微博應用程序能夠無需達到單域名cookie上限也能夠存儲更加結構化的數據。
爲了更好地操做子cookie,必須創建一系列新的方法。子cookie的解析序列和序列化會因子cookie的指望用途而略有不一樣並更加複雜些。
獲取子cookie的方法有兩個:get()和getAll()。其中get()獲取單個子cookie的值,getAll()獲取全部子cookie並將它們放入一個對象中返回,對象的屬性爲子cookie的名稱,對應值爲子cookie對應的值。get()方法接收兩個參數:cookie的名字和子cookie的名字。它其實就是調用getAll()獲取全部的子cookie,而後只返回所需的那一個(若是cookie不存在則返回null)。
get: function (name, subName){ var subCookies = this.getAll(name); 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, parts, result = {}; if (cookieStart > -1){ cookieEnd = document.cookie.indexOf(";", cookieStart) if (cookieEnd == -1){ cookieEnd = document.cookie.length; } 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; }
要設置子cookie,也有兩種方法:set()和setAll().爲了在同一個cookie中存儲多個子cookie,路徑、域和secure標誌必須一致;針對整個cookie的失效日期則能夠在任何一個單獨的子cookie寫入的時候同時設置。在這個set()方法中,第一步是獲取指定cookie名稱對應的全部子cookie。邏輯或操做符"||"用於當getAll()返回null時將subcookies設置爲一個新對象。而後,在subcookies對象上設置好子cookie值並傳給setAll()。
set: function (name, subName, value, expires, path, domain, secure) { var subcookies = this.getAll(name) || {}; subcookies[subName] = value; this.setAll(name, subcookies, expires, path, domain, secure); }, setAll: function(name, subcookies, expires, path, domain, secure){ var cookieText = encodeURIComponent(name) + "=", subcookieParts = new Array(), subName; for (subName in subcookies){ if (subName.length > 0 && subcookies.hasOwnProperty(subName)){ subcookieParts.push(encodeURIComponent(subName) + "=" + encodeURIComponent(subcookies[subName])); } } if (subcookieParts.length > 0){ 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; }
子cookie的最後一組方法是用於刪除子cookie的。普通cookie能夠經過將失效時間設置爲過去的時間的方法來刪除,可是子cookie不能這樣作。爲了刪除一個子cookie,首先必須獲取包含在某個cookie中的全部子cookie,而後再將餘下的子cookie的值保存爲cookie的值。
unset: function (name, subName, path, domain, secure){ var subcookies = this.getAll(name); if (subcookies){ delete subcookies[subName]; this.setAll(name, subcookies, null, path, domain, secure); } }, unsetAll: function(name, path, domain, secure){ this.setAll(name, null, new Date(0), path, domain, secure); }
整個SubCookieUtil部分代碼以下:
var SubCookieUtil = { get: function (name, subName){ var subCookies = this.getAll(name); 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, parts, result = {}; if (cookieStart > -1){ cookieEnd = document.cookie.indexOf(";", cookieStart) if (cookieEnd == -1){ cookieEnd = document.cookie.length; } 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; }, set: function (name, subName, value, expires, path, domain, secure) { var subcookies = this.getAll(name) || {}; subcookies[subName] = value; this.setAll(name, subcookies, expires, path, domain, secure); }, setAll: function(name, subcookies, expires, path, domain, secure){ var cookieText = encodeURIComponent(name) + "=", subcookieParts = new Array(), subName; for (subName in subcookies){ if (subName.length > 0 && subcookies.hasOwnProperty(subName)){ subcookieParts.push(encodeURIComponent(subName) + "=" + encodeURIComponent(subcookies[subName])); } } if (subcookieParts.length > 0){ 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; }, unset: function (name, subName, path, domain, secure){ var subcookies = this.getAll(name); if (subcookies){ delete subcookies[subName]; this.setAll(name, subcookies, null, path, domain, secure); } }, unsetAll: function(name, path, domain, secure){ this.setAll(name, null, new Date(0), path, domain, secure); } };
能夠像下面這樣使用上述方法:
//取得所有子cookie var data = SubCookieUtil.getAll("data"); alert(data.name); //"Nicholas" alert(data.book); //"Professional JavaScript" //逐個獲取子cookie alert(SubCookieUtil.get("data","name")); //"Nicholas" alert(SubCookieUtil.get("data","book")); //"Professional JavaScript" //設置兩個cookie SubCookieUtil.set("data", "name", "Nicholas"); SubCookieUtil.set("data", "book", "Professional JavaScript"); //設置所有子cookie和失效日期 SubCookieUtil.setAll("data",{name:"Nicholas", book:"Professional JavaScript"},new Date("January 1, 2010")); //修更名字的值,並修改cookie的失效日期 SubCookieUtil.set("data", "name", "Michael", new Date("February 1,2010"));
五、關於cookie的思考
還有一類cookie被稱爲「HTTP專有cookie」。HTTP專有cookie能夠從瀏覽器或者服務器設置,可是隻能從服務器端讀取。因爲全部的cookie都會由瀏覽器做爲請求頭髮送,因此在cookie中存儲大量信息會影響到特定域請求性能。cookie信息越大,完成對服務器請求的時間也就越長。儘管瀏覽器對cookie進行了大小限制,不過最好仍是儘量在cookie中少儲存信息,以免影響性能。
cookie的性質和它的侷限使得並不能做爲存儲大量信息的理想手段。
必定不要在cookie中存儲重要和敏感的數據。
微軟經過一個自定義行爲引入了持久化用戶數據的概念。要使用持久化用戶數據,首先必須以下所示,使用CSS在某個元素上指定userData行爲:
<div style="behavior:url(#default#userDate)" id="dataStore"></div>
一旦該元素使用了userDate行爲,那麼就能夠(1)使用setAttribute()方法在上面保存數據了。爲了將數據提交到瀏覽器緩存中,還必須(2)調用save()方法並告訴它要保存到的數據空間的名字。數據空間名字能夠徹底任意,僅用於區分不一樣數據集。(3)下一次頁面載入以後,可使用load()方法指定一樣的數據空間名稱來獲取數據。
var dataStore = document.getElementById("dataStore"); dataStore.setAttribute("name", "Nicholas"); dataStore.setAttribute("book","Professional JavaScript"); datastore.save("BookInfo"); //指定了數據空間的名稱爲BookInfo dataStore.load("BookInfo"); alert(dataStore.getAttribute("name")); //"Nicholas" alert(dataStore.getAttribute("book")); //"Professional JavaScript"
對load()的調用獲取了BookInfo數據空間中的全部信息,而且使數據能夠經過元素訪問;只有到載入確切完成以後數據方能使用。若是getAttribute()調用了不存在的名稱或者是還沒有載入的名稱,則返回null。
經過removeAttribute()方法明確指定要刪除某元素數據,只要指定屬性名稱。刪除以後,必須像下面這樣再次調用save()來提交更改。
dataStore.removeAttribute("name"); dataStore.removeAttribute("book"); dataStore.save("BookInfo");
和cookie同樣,IE用戶數據並不是安全的,因此不能存放敏感信息。