Javascript--離線應用與客戶端儲存

開發離線Web應用須要幾個步驟:php

  1. 首先確保應用知道設備是否能上網。css

  2. 應用必須能訪問必定的資源(圖像,Javascript,CSS),這樣才能正常工做。html

離線檢測

navigator.onLine

HTML5新定義的屬性,這個屬性值爲true表示設備能上網,值爲false表示離線設備。這個屬性的關鍵是瀏覽器必須知道設備可否訪問網絡,從而返回正確的值。web

相關事件類型:online和offline

HTML5還定義了兩個事件:online和offline。api

當網絡從離線變爲在線或者從在線變爲離線,分別觸發着兩個事件。瀏覽器

兼容性緩存

應用緩存

HTML5的應用緩存,或者簡稱爲appcache,是專門爲開發離線Web應用而設計的。Appcache就是從瀏覽器緩存中分出來的一塊緩存區。安全

要想在這個緩存中保存數據,可使用一個描述文件(manifest file),列出要下載和緩存的資源。即便用戶在離線狀態下按了刷新按鈕,您的應用也會正常加載和運行。服務器

使用緩存接口可爲您的應用帶來如下三個優點:cookie

  • 離線瀏覽 - 用戶可在離線時瀏覽您的完整網站

  • 速度 - 緩存資源爲本地資源,所以加載速度較快。

  • 服務器負載更少 - 瀏覽器只會從發生了更改的服務器下載資源。

清單文件

清單文件必須以 text/cache-manifest MIME 類型提供

清單文件格式
CACHE MANIFEST
# 2010-06-18:v2

# Explicitly cached 'master entries'.
CACHE:
/favicon.ico
index.html
stylesheet.css
images/logo.png
scripts/main.js

# Resources that require the user to be online.
NETWORK:
login.php
/myapi
http://api.twitter.com

# static.html will be served if main.py is inaccessible
# offline.jpg will be served in place of all images in images/large/
# offline.html will be served in place of all other .html files
FALLBACK:
/main.py /static.html
images/large/ images/offline.jpg
*.html /offline.html

清單可包括如下三個不一樣部分:CACHE、NETWORK 和 FALLBACK。

  1. CACHE: 這是條目的默認部分。系統會在首次下載此標頭下列出的文件(或緊跟在 CACHE MANIFEST 後的文件)後顯式緩存這些文件。

  2. NETWORK:
    此部分下列出的文件是須要鏈接到服務器的白名單資源。不管用戶是否處於離線狀態,對這些資源的全部請求都會繞過緩存。可以使用通配符。

  3. FALLBACK:
    此部分是可選的,用於指定沒法訪問資源時的後備網頁。其中第一個 URI 表明資源,第二個表明後備網頁。兩個 URI 必須相關,而且必須與清單文件同源。可以使用通配符。

NOTE這些部分可按任意順序排列,且每一個部分都可在同一清單中重複出現。

引用清單文件
<html manifest="example.appcache">
  ...
</html>

NOTE:

  • CACHE MANIFEST 字符串應在第一行,且必不可少。

  • 網站的緩存數據量不得超過 5 MB。

  • HTTP 緩存標頭以及對經過 SSL 提供的網頁設置的緩存限制將被替換爲緩存清單(也就是加了密的網站Are https URLs encrypted)。所以,經過 https 提供的網頁可實現離線運行。

  • 若是您要編寫的是針對 Chrome 網上應用店的應用,可以使用 unlimitedStorage 取消該限制。
    若是清單文件或其中指定的資源沒法下載,就沒法進行整個緩存更新進程。在這種狀況下,瀏覽器將繼續使用原應用緩存。

applicationCache對象

applicationCache對象有一個status屬性,屬性的值是常量,表示應用緩存的狀態。

applicationCache.status

  • 0:無緩存,即沒有與頁面相關的應用緩存

  • 1:閒置,即應用緩存未獲得更新

  • 2:檢查中,即正在下載描述文件並檢查更新

  • 3:下載中,即應用緩存正在下載描述文件中指定的資源

  • 4:更新完成,即應用緩存已經更新了資源,並且全部資源都已下載完畢,能夠經過swapCache()來使用了

  • 5:廢棄,即應用緩存的描述文件已經不存在了,所以頁面沒法再訪問應用緩存

相關事件類型

  • checking:在瀏覽器爲應用緩存查找更新時觸發

  • error:在檢查更新或下載資源期間發生錯誤時觸發

  • noupdate:在檢查描述文件無變化時粗放

  • downloading:在開始下載應用緩存時資源時觸發

  • progress:在文件下載應用緩存資源時粗放

  • updateready:在頁面新的應用緩存下載完畢,並且能夠經過swapCache()時觸發。

兼容性

Cookie

HTTP cookie,一般叫作cookie,最初是在客戶端用於存儲會話信息的。該標準要求服務器的任意HTTP請求發送Set-Cookie HTTP頭部做爲響應的一部分,其中包含會話信息。

// HTTP響應
HTTP/1.1 200 OK
Content-type: text/html
Set-cookie: name=value
Other-header: other-header-value 

// HTTP請求
GET /index.html HTTP/1.1
Cookie: name=value
Other-header: other-header-value

第一段會話過程
服務器:設置name=value的cookie,並將其做爲響應頭部的一部分,發送給瀏覽器。
瀏覽器:儲存name=value的cookie並將其做爲請求頭的一部分,發送給服務器。

NOTE:

  1. 會話過程的名稱和值都是通過URL編碼的。

  2. 每一個域的cookie總數是有限的,不一樣瀏覽器之間各有不一樣。當超過單個域名限制以後還有再設置cookie,瀏覽器就會清除之前的cookie。

  3. cookie在性質上是綁定在特定域名下的。當設定一個cookie後,再給建立它的域名發送請求時,都會包含這個cookie。

cookie構造

// HTTP響應
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;path=/;secure
Other-header: other-header-value 

// HTTP請求
GET /index.html HTTP/1.1
Cookie: name=value
Other-header: other-header-value
  • 名稱(name):一個惟一肯定cookie的名稱,cookie的名稱必須是通過 URL編碼的。

  • 值(value):儲存在cookie的字符串值,值必須被 URL編碼

  • 域:cookie對於那個域是有效的。全部向該域發送的請求中都會包含這個cookie信息。若是沒有明確設定,那麼這個域會被認做來自設置cookie的那個域。

  • 路徑:對於指定域中的那個路徑,應該向服務器發送cookie

  • 失效時間:表示cookie應該被刪除的那個時間戳

  • 安全標誌:指定後,cookie只能在SSL的鏈接時,才能發送到服務器

第二段會話過程
服務器:設置name=value的cookie,同時告訴瀏覽器

  1. 它會在格林威治時間2007年1月22日7:20:24失效。

  2. 對於全部wrox.com的子域和域名下(由path參數指定的)都有效

  3. 同時經過SSL鏈接才能傳輸。
    並將其做爲響應頭部的一部分,發送給瀏覽器。

瀏覽器:儲存name=value的cookie並將其做爲請求頭的一部分,發送給服務器。

Note:後四個,域/路徑/失效時間/安全標誌都是 服務器給瀏覽器的指示。以指定什麼時候該發送cookie。這些參數並不會做爲發送到服務器的標誌,名值對纔會被髮送。只有cookie的名字和值是必須的。

document.cookie

用來獲取屬性值時,document.cookie返回當前頁面可用的(根據cookie的域,路徑,失效時間和安全設置)全部的cookie的字符串。

[]()

當用來設置值得時候,document.cookie能夠用來設置新的cookie字符串。這個cookie字符串會用來解釋並添加到現有的cookie中。設置的值並不會覆蓋cookie,除非設置的值已經存在。

var cookieUtil = {

    get: function ( name ) {

        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.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, domain, secure ) {

        this.set( name, '' , new Date(0), path, domain, secure)
    }
}

子cookie

爲了繞開 瀏覽器 的單域名下的cookie數限制,一種開發人員使用了一種稱爲子cookie(subcookie)的概念。子cookie是存放在單個cookie中的更小段的數據。也就是使用cookie值來儲存多個名稱值對兒。

子cookie通常以查詢字符串的格式進行格式化。而後這些值可使用單個cookie進行儲存和訪問
name=name1=value1&name2=value2&name3=value3

上面展現瞭如何寫入,讀取和刪除cookie,下面展現操做子cookie的方法。

var SubCookieUtil = {

    get: function ( name, subName ) {

        var cookieValue = this.getAll( name );

        if( subName ) {

            return result[subName];
        }
        else {

            return null;
        }
    },

    getAll: function ( name ) {

        var cookieName  = encodeURIComponent( name ) + '=',
            cookieStart = document.cookie.indexOf( cookieName ),
            cookieValue = null,
            cookieEnd,
            result      = {};

        if( cookieStart > -1 ) {

            cookieEnd = document.cookie.indexOf( ';', cookieStart );

            if( cookieEnd === -1 ) {

                cookieEnd = document.cookie.length;
            }
        }

        cookieValue = document.cookie.substring( cookieStart+cookieName.length, cookieEnd );

        decodeURIComponent( cookieValue );

        if( cookieValue.length > 0 ) {

            subCookies = cookieValue.split( '&' );

            for( var i = 0; i < subCookies.length; i++  ) {

                var parts = subCookies[i].split( '=' );

                result[parts[0]] = parts[1];

            }

            return result;
        }
        else {

            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 ) + '=',
            subCookiesParts = [],
            subName;

        for( subName in subCookies ) {

            if( subCookies.hasOwnProperty( subName ) ) {

                subCookiesParts.push( encodeURIComponent(subName) + '=' + encodeURIComponent(subCookies[subName]) );
            }
        }

        if( subCookiesParts.length > 0 ) {

            cookieText += subCookiesParts.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;

    },

    unsetAll: function ( name, subCookies, expires, path, domain, secure ) {

        this.set( name, null, new Date(0), path, domain, secure );
    },

    unset: function ( name, subName, expires, path, domain, secure ) {

        var subCookies = this.get( name );

        if( subCookies ) {

            delete subCookies[subName]
            this.setAll( name, subCookies, expires, path, domain, secure );
        }

    }
}

關於cookie的性能與安全

  1. 因爲全部的cookie都會由瀏覽器做爲請求頭髮送,因此在cookie中存儲大量信息會影響到特定域的請求性能。cookie信息越大,完成對服務器請求的時間也就越長。

  2. cookie數據並不是存儲在一個安全環境中,其中包含的任何數據均可被他人訪問。因此必定不要在cookie中儲存重要和敏感的數據。

IE用戶數據

在IE5.0中,微軟經過一個自定義行爲引入了持久化用戶數據的概念。用戶數據容許每一個文檔最多128KB數據,每一個域名最多1MB數據。要使用持久化用戶數據,首先,必須以下所示,使用CSS在某個元素上指定userData行爲。

`<div style="behavior:url(#default#userData)" id="dataStore"></div>`
var dataStore = getElementById('dataStore');
// 寫入數據
dataStore.setAttribute('name','Nicholas');
dataStore.setAttribute('book','Professional Javascript');
dataStore.save('BookInfo');

// 獲取數據
dataStore.load('BookInfo'); 
// 訪問數據
console.log(dataStore.getAttribute('name'));
console.log(dataStor.getAttribute('book'));

// 刪除數據
dataStore.removeAttribute( 'name' );
dataStore.removeAttribute( 'book' );
dataStore.save('BookInfo');

Note:

  • 和cookie同樣,IE用戶數據並不是安全的,全部不能存放敏感信息。

  • 用戶數據默認是能夠跨越會話持久存在的,同時也不會過時

  • 要訪問某個數據空間,腳本運行的頁面必須來自於同一個域名,路徑並使用與進行存儲的腳本一樣的協議。

Web存儲機制

Web Storage的兩個主要目標是

  • 提供一種在cookie以外存儲會話數據的途徑

  • 提供一種存儲大量能夠跨會話存在的數據的機制。

兼容性

Note:與其餘客戶端儲存方案相比,WebStorage一樣也有限制,這些限制因瀏覽器圍而異。通常來講,對存儲空間大小的限制都是以每一個來源(協議,域名,端口號)爲單位的。換句話說,每一個來源都要固定大小的空間用於保存本身的數據。考慮到這個限制,就要注意分析和控制每一個來源中有多少頁面須要保存數據。

webStorage限制測試

Storage類型

Storage提供最大的空間(因瀏覽器而異)來儲存名值對。

sessionStorage instanceof Storage // true
localStorage instanceof Storage// true

localStorage.__proto__ === Storage.prototype // true
sessionStorage.__proto__ === Storage.prototype // true

由上可知,sessionStorage對象localStorage對象都爲Storge類型的實例。所以它們均可以訪問到Storage類型的原型對象上的方法。有以下這些方法。

  • clear():刪除全部值;Firefox中沒有實現

  • getItem(name):根據指定的名字name獲取對應的值

  • key(index):得到index位置處的值得名字

  • removeItem(name):刪除由name指定的名值對

  • setItem(nname, value):爲指定的name設置一個對應的值

同時可使用length屬性判斷Storage對象中有多少名值對。但沒法判斷對象中全部數據的大小,不過IE8提供了一個remainingSpace屬性,用於獲取還可使用的存儲空間的字節數。

Note:Storge類型只能存儲字符串。非字符串的數據在存儲以前會被轉換爲字符串。

sessionStorage對象

爲每個給定的源(given origin)維持一個獨立的存儲區域,該存儲區域在頁面會話期間可用(即只要瀏覽器處於打開狀態,包括頁面從新加載和恢復)。

sessionStorage對象主要用於僅針對會話的小段數據的存儲。它儲存特定於某個會話的數據,也就是該數據只保持到瀏覽器關閉。這個對象就像會話cookie,也會在瀏覽器關閉後消失。

  1. 存儲在sessionStorage中的數據能夠跨越頁面而存在,同時若是瀏覽器支持,瀏覽器崩潰重啓後依然可用(Firefox和Webkit都支持,IE不行)。

  2. sessionStorage對象綁定於某個服務器會話,全部當文件在本地運行的時候是不可用的。

  3. 存儲在sessionStorage的數據只能由最初給對象儲存數據的的頁面訪問到,因此對多頁面應用有限制。

// 寫入數據
sessionStorage.setItem( 'name', 'Nicholas' );

// 訪問數據
sessionStorage.getItem( 'name' );

// 迭代數據
for( var i = 0,len = sessionStorage.length; i < len; i++ ) {
    
    var key = sessionStorge.key( i );
    var value = sessionStorage.getItem( key );
    console.log( key + '=' + value );
}
for(key in sessionStorage) {

    var value = sessionStorage.getItem( key );
    console.log( key + '=' + value );
}

// 刪除數據
sessionStorage.removeItem( 'name' );

Note:不一樣瀏覽器寫入數據的方法略有不一樣。Firefox和Webkit實現了同步寫入,全部添加到儲存空間的數據是馬上被提交的。而IE的實現是異步寫入數據,因此在設置數據和將數據寫入磁盤之間可能有一些延遲。對於少許數據而言,這個差別是能夠忽略的。對於大量數據,你會發現IE比其餘瀏覽器更快的恢復執行,由於它會跳過實際的磁盤寫入過程。
在IE8中可強制把數據寫入磁盤,以下。

// 防止代碼執行的時候,不會發生其餘磁盤寫入操做
sessionStorage.begin();

sessionStorage.name = 'Nicholas';

// 確保name的值在調用commit()以後馬上寫入磁盤
sessionStorage.commit();

localStorage對象

localStorage 一樣的功能,可是在瀏覽器關閉,而後從新打開後數據仍然存在。

localStorage實現跨越會話存儲數據,在HTML5規範中做爲持久保存客戶端數據的方案取代了globalStorage。
規則:要訪問同一個localStorage對象,頁面必須來自於同一個域名(子域名無效),使用同一種協議,在同一個端口上。

由於localStorage是Storage的實例,全部能夠像使用sessionStorage同樣來使用它。

storage事件

對Storage對象進行任何修改,都會在文檔上觸發storage事件。

storge事件對象

  • domain:發生變化的儲存空間的域名

  • key:設置或者刪除的鍵名

  • newVaule:若是是設置值,則爲新值;若是是刪除鍵,則爲null

  • oldValue:鍵被更改以前的值

相關文章
相關標籤/搜索