JS學習筆記(第23章)(離線應用與客戶端存儲1)

所謂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屬性,屬性的值是常量,表示應用緩存的以下當前狀態。網絡

  • 0:無緩存,即沒有與頁面相關的應用緩存
  • 1:閒置,即應用緩存未獲得更新
  • 2:檢查中,即正在下載描述文件並檢查更新
  • 3:下載中,即應用緩存正在下載描述文件中的指定資源
  • 4:更新完成,即應用緩存已經更新了資源,並且全部資源都已下載完畢,能夠經過swapCache()來使用了
  • 5:廢棄,即應用緩存的描述文件已經不存在了,所以頁面沒法再訪問緩存。

(3)應用緩存還有不少相關的事件,表示其狀態的改變。如下是這些事件:app

  • checking:在瀏覽器爲應用緩存查找更新時觸發
  • error:在檢查更新或下載資源其期間發生錯誤時觸發
  • noupdate:在檢查描述文件發現文件無變化時觸發
  • downloading:在開始下載應用緩存資源時觸發
  • progress:在文件下載應用緩存的過程當中持續不斷的觸發
  • updateready:在頁面新的應用緩存下載完畢且能夠經過swapCache()使用時觸發
  • cached:在應用緩存完整可用時觸發

通常來說,這些時間會隨着頁面加載按上述順序依次觸發,不過經過調用update()方法也能夠手工干預,讓應用緩存爲檢查更新而觸發上述事件。dom

applicationCache.update();

update()已經一經調用,應用緩存就會去檢查描述文件是否更新(觸發checking事件),而後就像頁面剛剛加載同樣,繼續執行後續操做。若是觸發了cached事件,就說明應用緩存已經準備就緒,不會再發生其餘操做了。若是觸發了updateready事件,則說明新版本的應用緩存已經可用,而此時你須要調用swapCache()來啓用新應用緩存。

Event.addHandler(applicationCache, "updateready", function() {
    applicationCache.swapCache();
});

三、數據存儲

3.1 Cookie

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由瀏覽器保存的一下幾塊信息構成。(名稱、值、域、路徑、失效時間、安全標誌)

  • 名稱:一個惟一肯定cookie的名稱。cookie名稱是不區分大小寫的,可是實踐中最好將cookie名稱看做是區分大小寫的。cookie的名稱必須是通過URL編碼的。
  • 值:存儲在cookie中的字符串值。值必須被URL編碼。
  • 域:cookie對於哪一個域是有效的。全部向該域發送的請求都會包含這個cookie信息。這個值能夠包含子域(subdomain,如www.wrox.com),也能夠不包含它(如.wrox.com,則對於wrox.com的全部子域都有效)。若是沒有明確設定,那麼這個域會被認做來自設置cookie的那個域。
  • 路徑:對於指定域中的那個路徑,應該向服務器發送cookie。例如,你能夠指定cookie只有從http://www.wrox.com/books/ 中才能訪問,那麼http://www.wrox.com 的頁面就不會發送cookie信息,即便請求都是來自於同一個域的。
  • 失效時間:表示cookie什麼時候應該被刪除的時間戳(也就是,什麼時候應該中止向服務器發送這個cookie)。默認狀況下,瀏覽器會話結束時即將全部cookie刪除;不過也能夠本身設置刪除時間。這個值是個GMT格式的日期,用於指定應該刪除cookie的準確時間。cookie可在瀏覽器關閉後依然保存在用戶的機器上。若是你設置的失效日期是個之前的時間,則cookie會被當即刪除。
  • 安全標誌:指定後,cookie只有在使用SSL鏈接的時候才發送到服務器。例如,cookie信息只能發送給https://www.wrox.com,而 http://www.wrox.com 的請求則不能發送。

每一段信息都做爲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中存儲重要和敏感的數據。

3.2 IE用戶數據

微軟經過一個自定義行爲引入了持久化用戶數據的概念。要使用持久化用戶數據,首先必須以下所示,使用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用戶數據並不是安全的,因此不能存放敏感信息。

3.3 Web存儲機制

3.4 IndexedDB

相關文章
相關標籤/搜索