1. 程式人生 > >JS頁面重新整理保持資料不丟失

JS頁面重新整理保持資料不丟失

下面是 DOM Storage 的介面定義:

 interface Storage { 
  readonly attribute unsigned long length; 
  getter DOMString key(in unsigned long index); 
  getter any getItem(in DOMString key); 
  setter creator void setItem(in DOMString key, in any data); 
  deleter void removeItem(in DOMString key); 
  void clear(); 
 };

HTML5 是下一代 HTML 標準,開始吸引越來越多人的目光。HTML5 的 DOM Storage 機制提供了一種方式讓程式設計師能夠把資訊儲存到本地的計算機上,在需要時獲取。這點和 cookie 相似,區別是 DOM Storage 提供了更大容量的儲存空間。

目前,在客戶端儲存資料使用最多的是 cookie,但 cookie 的大小上限為 4KB,並且每次請求一個新頁面時 cookie 都會被髮送過去。更多的儲存空間需要瀏覽器本身或是外掛的支援,例如只在 Internet Explorer 上使用的 userData,需要額外安裝外掛的 Google Gears 和 Flash。現在,HTML5 提供了一種標準的介面,使程式設計師可以簡單地訪問儲存的資料。由於鍵值對儲存在本地計算機上,在頁面載入完畢後可以通過 JavaScript 來操作這些資料。

DOM Storage

示例應用程式:使用者註冊

本文使用的示例應用程式是一個簡單的使用者註冊過程,表單包含三個欄位:name、age 和 address,我們將其拆分為兩個表單,分兩個頁面顯示。藉助簡化了的資料模型,主要介紹如何利用 DOM Storage 功能處理表單跨頁問題。

DOM Storage 兩個分類

DOM Storage 分為 sessionStorage 和 localStorage。

localStorage 物件和 sessionStorage 物件使用方法基本相同,它們的區別在於作用的範圍不同。sessionStorage 用來儲存與頁面相關的資料,它在頁面關閉後無法使用。而 localStorage 則持久存在,在頁面關閉後也可以使用。

DOM Storage 介面

下面是 DOM Storage 的介面定義:

 interface Storage { 
  readonly attribute unsigned long length; 
  getter DOMString key(in unsigned long index); 
  getter any getItem(in DOMString key); 
  setter creator void setItem(in DOMString key, in any data); 
  deleter void removeItem(in DOMString key); 
  void clear(); 
 };

length:返回當前儲存在 Storage 物件中的鍵值對數量。

key(index):返回列表中第 n 個鍵的名字。Index 從 0 開始。

getItem(key):返回指定鍵對應的值。

setItem(key, value):存入一個鍵值對。

removeItem(key) :刪除指定的鍵值對。

clear():刪除 Storage 物件中的所有鍵值對。

通常,使用最多的方法是 getItem 和 setItem。

以 sessionStorage 為例:

儲存鍵值對:

window.sessionStorage.setItem(“key1”, value1);

通過鍵名來讀取值:

var value1 = window.sessionStorage.getItem(“key1”);

判斷瀏覽器是否支援 DOM Storage

要使用 DOM Storage,首先,需要檢視當前的瀏覽器是否支援。目前 Internet Explorer 8.0 以上,Firefox 3.5 以上,Chrome 4.0 以上都是支援 DOM Storage 的。

如果瀏覽器不支援 DOM Storage,可以用其他的方法作為備選,本文還使用 Dojo 提供的 dojox.storage 模組來實現相同的功能。

清單 1. 檢視瀏覽器是否支援 DOM Storage
 //sessionStorage 
 if(window.sessionStorage){ 
    alert(“support sessionStorage”); 
 }else{ 
    alert(“not support sessionStorage”); 
    // 不支援 sessionStorage 
    // 用 dojox.storage 來實現相同功能
 } 

 //localStorage 
 if(window.localStorage){ 
    alert(“support localStorage”); 
 }else{ 
    alert(“not support localStorage”); 
    // 不支援 localStorage 
    // 用 dojox.storage 來實現相同功能
 }

下面是使用者註冊的兩個表單。清單 2 中的第一個表單有兩個欄位 name 和 age 需要使用者填寫內容。填寫完後點擊 Next 按鈕進入下一個頁面,此時函式 saveToStorage 會被呼叫,把在該頁面輸入的兩個欄位的值儲存到 sessionStorage 物件中。

當從下一個頁面退回到本頁面時,使用 windows.onload 在載入頁面的時候將資料從 sessionStorage 中取出,並顯示在輸入框中,方便使用者修改。

另外,給物件賦值除了用 setItem 方法外,也可以用 window.sessionStorage.key1 = “value1”。

清單 2. 第一個表單頁面
<script type="text/javascript"> 
 // 當退回到第一個頁面時,從 sessionStorage 得到使用者之前輸入的值並顯示在頁面,方便修改
 window.onload = function(){ 
    if (window.sessionStorage) { 
        var name = window.sessionStorage.getItem("name"); 
        var age = window.sessionStorage.getItem("age"); 
        if (name != "" || name != null){ 
            document.getElementById("name").value = name; 
        } 
        if (age != "" || age != null){ 
            document.getElementById("age").value = age; 
        } 
    }else 
    { 
        // 不支援 sessionStorage,用 Dojo 實現相同功能
    } 
 }; 

 // 將資料儲存到 sessionStorage 物件中
 function saveToStorage() { 
    //sessionStorage 
    if (window.sessionStorage) { 
        var name = document.getElementById("name").value; 
        var age = document.getElementById("age").value; 
        window.sessionStorage.setItem("name", name); 
        window.sessionStorage.setItem("age", age); 
        window.location.href="form2.html"; 
    } else { 
        // 不支援 sessionStorage,用 Dojo 實現相同功能
    } 
 } 
 </script> 

 <form action="./form2.html"> 
    <input type="text" name="name" id="name"> 
    <input type="text" name="age" id="age"> 
    <input type="button" value="Next" onclick="saveToStorage()"></input> 
 </form>

清單 3 的第二個頁面有一個 address 欄位。當用戶填寫完畢後,點選 Submit 按鈕提交頁面,此時 addStorageValue 函式被呼叫,把儲存在 sessionStorage 中的 name 和 age 值先賦給當前表單的兩個隱藏欄位,隨後一起提交給下一個處理表單的頁面。最後呼叫 removeItem 函式刪除 name 和 age 值。

如果使用者需要修改第一個頁面填寫的內容,可以點選 Back 按鈕回到前一個頁面,使用者在前一個頁面已經填寫的內容會出現在 text 框中。

清單 3. 第二個表單頁面

 <script type="text/javascript"> 
 // 將保持在 sessionStorage 中的資料賦給表單的隱藏屬性
 function addStorageValue() { 
    //sessionStorage 
    if (window.sessionStorage) { 
        var name = window.sessionStorage.getItem("name"); 
        var age = window.sessionStorage.getItem("age"); 
        document.getElementById("name").value = name; 
        document.getElementById("age").value = age; 
        window.sessionStorage.removeItem("name"); 
        window.sessionStorage.removeItem("age"); 
    } else { 
        // 不支援 sessionStorage,用 Dojo 實現相同功能
    } 
 } 

 function backToPreviousForm() { 
    window.location.href="form1.html"; 
 } 
 </script> 

 <form action="./form3.php" method="post"> 
    <input type="hidden" name="name" id="name"> 
    <input type="hidden" name="age" id="age"> 
    <input type="text" name="address" id="address"> 
    <input type="button" value="Back" onclick="backToPreviousForm()"> 
    <input type="submit" value="Submit" onclick="addStorageValue()"></input> 
 </form>

使用 DOM Storage 需要注意的幾點

儲存在 Storage 物件的資料型別

當使用 DOM Storage 進行本地儲存時,任何資料格式在 Storage 物件中都以字串型別儲存,所以如果儲存的資料不是字串,在讀取的時候需要自己進行型別的轉換。這裡我們使用 JSON 將物件序列化之後再儲存。

JSON (JavaScript Object Notation) 是一種輕量級的資料交換格式。易於人閱讀和編寫,同時也易於機器解析和生成。目前,JSON 已經是 JavaScript 標準的一部分,主流的瀏覽器對 JSON 支援都非常完善。

本文用到兩個相關的函式

JSON.parse() 函式會把 JSON 物件轉換為原來的資料型別。

JSON.stringify() 函式會把要儲存的物件轉換成 JSON 物件儲存。

在清單 4 中,先把一個布林型的資料存到 Storage 物件中,然後再取出,可以看到布林型別的資料在取出的時候變為字串。接下來換一種方式儲存資料,先用 JSON.stringify 方法序列化資料,然後儲存到 Storage 物件中,在取出的時候用 JSON.parse 方法進行反序列化,可以看到讀取出的資料還是布林型別。

另外,使用 JSON 儲存一個字串,通過 Chrome 的 Storage 工具,可以看到存入的字串兩邊有雙引號,這個雙引號表示存入的是一個字串。當用 JSON 表示一個簡單的字串時,會在字串兩邊加上雙引號。最後,該頁面載入後的輸出如下:

string1 boolean2 string3

清單 4. 使用 JSON 對 DOM Storage 的複雜資料進行處理

 // 生成一個 Boolean 型別的變數 data1 
 var data1 = new Boolean(true); 

 // 不用 JSON 處理資料
 sessionStorage["key1"] = data1; 
 if(sessionStorage["key1"] == "true"){ 
    // 從 Storage 物件讀取出來的資料 data1 變為 String 型別
    document.write("string1 "); 
 } 

 // 使用 JSON 處理資料 data1 
 sessionStorage["key2"] = JSON.stringify(data1); 
 if(JSON.parse(sessionStorage["key2"]) == true){ 
    // 從 Storage 物件讀取的資料 data1,用 JSON 將變數轉換為原來的 Boolean 型別
    document.write("boolean2 "); 
 } 

 // 生成一個 String 型別的變數
 var data2 = new String("true"); 
 // 使用 JSON 處理資料,在 Storage 物件中儲存的是 “string”
 sessionStorage["key3"] = JSON.stringify(data2); 
 data2 = JSON.parse(sessionStorage["key3"]); 
 if(data2 == "true"){ 
    // 變數轉換回來還是 String 型別
    document.write("string3"); 
 }

使用 Chrome 瀏覽器可以檢視當前的 sessionStorage 和 localStorage 的鍵值對。在工具欄選擇“工具”到“開發人員工具”到“Resources”到“Local Storage”或“Session Storage”, 可以檢視 key 和 value。

圖 1. Chrome 瀏覽器的 Storage 工具欄
圖 1. Chrome 瀏覽器的 Storage 工具欄

綜上所述,我們可以如清單 5 一樣,在載入頁面的時候用 JSON 轉換資料型別,在離開頁面的時候將資料儲存為 JSON 物件。這樣,儲存在 Storage 中任何型別的資料在讀取的時候都可以轉換為原來的型別。

清單 5. 使用 JSON 對 DOM Storage 的複雜資料進行處理
<script type="text/javascript"> 
 var value; 
 function loadValue() { 
    value1 = JSON.parse(window.sessionStorage.getItem(“key1”)); 
 } 
 function saveValue() { 
    window.sessionStorage.setItem(“key1”) = JSON.stringify(value1); 
 } 

 window.addEventListener(“load”, loadValue. true); 
 window.addEventListener(“unload”, saveValue. true); 
 </script>

空間大小

HTML5 的建議是每個網站提供給 Storage 的空間是 5MB,一般來說足夠存字串。如果存入的資料太大,有些瀏覽器如 Chrome 會丟擲 QUOTA_EXCEEDED_ERR 異常。所以雖然 DOM Storage 提供的空間比 cookie 要大很多,但在使用需要注意限制。

圖 2. Chrome 瀏覽器丟擲異常
圖 2. Chrome 瀏覽器丟擲異常

安全性

一般不要在客戶端儲存敏感的資訊,使用 localStorage、globalStorage 等在客戶端儲存的資訊都非常容易暴露。應該在完成資料儲存後使用 clear 或者 removeItem 方法清除儲存在 Storage 物件中的資料。

儲存事件驅動

如果想在儲存成功或修改儲存的值時執行一些操作,可以用 DOM Storage 介面提供的事件。可以使用如下方法註冊事件:

window.addEventListener(“storage”, handleStorageEvent, false);

儲存事件介面定義

interface StorageEvent : Event {
readonly attribute DOMString key;
readonly attribute any oldValue;
readonly attribute any newValue;
readonly attribute DOMString url;
readonly attribute Storage storageArea;
void initStorageEvent(in DOMString typeArg, in boolean canBubbleArg, 
in boolean cancelableArg, in DOMString keyArg, in any oldValueArg, 
in any newValueArg, in DOMString urlArg, in Storage storageAreaArg);
};

key:發生改變的鍵。

oldValue:鍵改變之前的值。

newValue:鍵改變之後的值。

url:觸發儲存事件的頁面 url。

在清單 6 中註冊完儲存事件後,當 sessionStorage 或者 localStorage 物件的值發生改變時,會觸發 handleStorageEvent 函式,在頁面顯示發生改變的鍵和改變之前與之後的值。

清單 6. 新增儲存事件
 // 顯示儲存事件的相關內容
 function handleStorageEvent(e) { 
    document.write(“key” + e.key + “oldValue” + e.oldValue + “newValue” + e.newValue); 
 } 
 // 新增儲存事件監聽
 window.addEventListener(“storage”, handleStorageEvent, false);

使用 Dojo 實現之前使用者註冊的功能

Dojo 是一個 JavaScript 實現的開源工具包,很大程度上遮蔽了瀏覽器之間的差異性。Dojo 擴充套件庫 (dojox) 是 Dojo 在其基本庫、核心庫和 Dijit 庫的基礎上提供的一個非常豐富的元件倉庫。本文用到的 dojox.storage 模組能夠將資料儲存在本地儲存中,實現和之前 DOM Storage 一樣的功能。

由於一些老版本瀏覽器不支援 HTML5,我們還可以用 Dojo 來實現之前使用者註冊的功能。相對於 HTML5 的 DOM Storage 介面,Dojo 的 dojox.storage.Provider 介面提供的方法更多。這裡我們列出幾個常用的方法。

get(key, namespace):返回指定鍵對應的值。

put(key, value, resultsHandler, namespace):存入一個鍵值對。

remove(key, namespace):刪除指定的鍵值對。

clear(namespace):刪除物件中的所有鍵值對。

現在對第一個表單的 JavaScript 程式碼做部分修改,並在頁面中引入 dojox.storage 模組。這樣,程式在不支援 HTML5 的瀏覽器中能夠通過呼叫 Dojo 提供的方法正常執行。dojo.require("dojox.storage") 表示引入 dojox.storage 功能模組。然後通過 dojox.storage.manager.isInitialized() 檢視 dojox.storage.manager 是否已經初始化,如果沒有的話,則需要等待其初始化完成之後,再進行儲存操作。

清單 7. 經過修改後的第一個表單頁面的部分程式碼
 <script type="text/javascript"> 
 dojo.require("dojox.storage"); 
 // 當退回到第一個頁面時,從 Storage 中得到使用者之前輸入的值並顯示在頁面,方便修改
 // 這裡先進行 dojox.storage.manager 的初始化
 if(!dojox.storage.manager.isInitialized()){ 
    dojo.connect(dojox.storage.manager, "loaded", saveAndLoad); 
 } else{ 
    dojo.connect(dojo, "loaded", saveAndLoad); 
 } 
 function saveAndLoad(){ 
    var name; 
    var age; 
    //sessionStorage 
    if (window.sessionStorage) { 
        name = window.sessionStorage.getItem("name"); 
        age = window.sessionStorage.getItem("age"); 
        if (name != "" || name != null){ 
            document.getElementById("name").value = name; 
        } 
        if (age != "" || age != null){ 
            document.getElementById("age").value = age; 
        } 
    }//dojox.storage 
    else 
    { 
        name = dojox.storage.get("name"); 
        age = dojox.storage.get("age"); 
        if (typeof name != "undefined" ){ 
            document.getElementById("name").value = name; 
        } 
        if (typeof age != "undefined" ){ 
            document.getElementById("age").value = age; 
        } 
    } 
 } 

 // 儲存資料
 function saveToStorage() { 
    var name = document.getElementById("name").value; 
    var age = document.getElementById("age").value; 
    //sessionStorage 
    if (window.sessionStorage) { 
        window.sessionStorage.setItem("name", name); 
        window.sessionStorage.setItem("age", age); 
    }//dojox.storage 
    else { 
        dojox.storage.put("name", name); 
        dojox.storage.put("age", age); 
    } 
    window.location.href="form2.html"; 
 } 
 </script>
清單 8. 經過修改後的第二個表單頁面的部分程式碼
 <script type="text/javascript"> 
 dojo.require("dojox.storage"); 
 // 將儲存在 sessionStorage 中的資料賦給表單的隱藏屬性
 function addStorageValue() { 
    var name; 
    var age; 
    //sessionStorage 
    if (window.sessionStorage) { 
        name = window.sessionStorage.getItem("name"); 
        age = window.sessionStorage.getItem("age"); 
        document.getElementById("name").value = name; 
        document.getElementById("age").value = age; 
        window.sessionStorage.removeItem("name"); 
        window.sessionStorage.removeItem("age"); 
    }//dojox.storage 
    else { 
        name = dojox.storage.get("name"); 
        age = dojox.storage.get("age"); 
        document.getElementById("name").value = name; 
        document.getElementById("age").value = age; 
        dojox.storage.remove("name"); 
        dojox.storage.remove("age"); 
    } 
 } 

 function backToPreviousForm() { 
    window.location.href = "form1.html"; 
 } 
 </script>