前面的話
現代Web應用中頻繁使用的一項功能就是表單數據的序列化,XMLHttpRequest 2級為此定義了FormData類型。FormData為序列化表單以及創建與表單格式相同的數據提供了便利。本文將先介紹表單編碼,然后過渡到表單序列化,最后引出FormData的核心概念
表單編碼
當用戶提交表單時,表單中的數據(每個表單元素的名字和值)編碼到一個字符串中並隨請求發送。默認情況下,HTML表單通過POST方法發送給服務器,而編碼后的表單數據則用做請求主體
對表單數據使用的編碼方案相對簡單:對每個表單元素的名字和值執行普通的URL編碼(使用十六進制轉義碼替換特殊字符),使用等號把編碼后的名字和值分開,並使用"&"符號分開名/值對。一個簡單表單的編碼如下所示
find=pizza&zipcode=01234&radius=1km
表單數據編碼格式有一個正式的MIME類型
application/x-www-form-urlencoded
當使用POST方法提交這種順序的表單數據時,必須"Content-Type"請求頭為這個值
[注意]這種類型的編碼並不需要HTML表單,在Ajax應用中,希望發送給服務器的很可能是一個javascript對象
前面展示的數據變成javascript對象的表單編碼形式可能是:
{find: "pizza", zipcode: 01234, radius: "1km"}
表單編碼在Web上如此廣泛使用,同時所有服務器端的編程語言都能得到良好的支持,所以非表單數據的表單編碼通常也是容易實現的事情
下面代碼展示了如何實現對象屬性的表單編碼
function encodeFormData(data){ if (!data) return ""; //一直返回字符串 var pairs = []; //用於保存名值對 for(var name in data){ if (!data.hasOwnProperty(name)){ continue; //跳過繼承屬性 } if (typeof data[name] === "function"){ continue; //跳過方法 } var value = data[name].toString(); //把值轉換成字符串 name = encodeURIComponent(name.replace("%20", "+")); //編碼名字 value = encodeURIComponent(value.replace("%20", "+"));//編碼值 pairs.push(name + "=" + value); // 存入名值對 } return pairs.join('&'); //返回使用'&'連接的名值對 }
var data = {name:'小火柴',age:28,sender:'male'}; //name=%E5%B0%8F%E7%81%AB%E6%9F%B4&age=28&sender=male console.log(encodeFormData(data));
表單序列化
隨着Ajax的出現,表單序列化已經成為一種常見需求。在javascript中,可以利用表單字段的type屬性,連同name和value屬性一起實現對表單的序列化。在編寫代碼之前,有必須先搞清楚在表單提交期間,瀏覽器是怎樣將數據發送給服務器的
1、對表單字段的名稱和值進行URL編碼,使用和號(&)分隔
2、不發送禁用的表單字段
3、只發送勾選的復選框和單選按鈕
4、不發送type為"reset"和"button"的按鈕
5、多選選擇框中的每個選中的值単獨一個條目
6、在單擊提交按鈕提交表單的情況下,也會發送提交按鈕;否則,不發送提交按鈕。也包括type為"image"的<input>元素
7、<select>元素的值,就是選中的<option>元素的value特性的值。如果<option>元素沒有value特性,則是<option>元素的文本值
在表單序列化過程中,一般不包含任何按鈕字段,因為結果字符串很可能是通過其他方式提交的。除此之外的其他上述規則都應該遵循
在下面表單序列化serialize()函數中,首先定義了一個名為parts的數組,用於保存將要創建的字符串的各個部分。然后,通過for循環迭代每個表單字段,並將其保存在field變量中。在獲得了一個字段的引用之后,使用switch語句檢測其type屬性
序列化過程中最麻煩的就是<select>元素,它可能是單選框也可能是多選框。為此,需要遍歷控件中的每一個選項,並在相應選項被選中的情況下向數組中添加一個值。對於單選框,只可能有一個選中項,而多選框則可能有零或多個選中項。這里的代碼適用於這兩種選擇框,至於可選項的數量則是由瀏覽器控制的。在找到一個選中項之后,需要確定使用什么值。如果不存在value特性,或者雖然存在該特性,但值為空字符串,都要使用選項的文本來代替。為檢査這個特性,在DOM兼容的瀏覽器中需要使用hasAttribute()方法,而在IE7-中需要使用特性的specified屬性
如果表單中包含<fieldset>元素,則該元素會出現在元素集合中,但沒有type屬性。因此,如果type屬性未定義,則不需要對其進行序列化。同樣,對於各種按鈕以及文件輸入字段也是如此(文件輸入字段在表單提交過程中包含文件的內容;但是,這個字段是無法模仿的,序列化時一般都要忽略)
對於單選按鈕和復選框,要檢查其checked屬性是否被設置為false,如果是則退出switch語句。如果checked屬性為true,則繼續執行default語句,即將當前字段的名稱和值進行編碼,然后添加到parts數組中。函數的最后一步,就是使用join()格式化整個字符串,也就是用和號來分隔每一個表單字段
最后,serialize()函數會以査詢字符串的格式輸出序列化之后的字符串
function serialize(form){ var parts = [],field = null,option,optValue; for (var i=0, len=form.elements.length; i < len; i++){ field = form.elements[i]; switch(field.type){ //單選或多選的<select>控件 case "select-one": case "select-multiple": //如果該<select>控件設置為name屬性 if (field.name.length){ for (var j=0,optLen = field.options.length; j < optLen; j++){ //選擇<option>控件 option = field.options[j]; //如果該<option>控件被選中 if (option.selected){ optValue = ""; //測試<option>控件的value屬性,如果沒有設置,則讀取其text屬性 //IE7-瀏覽器不支持hasAttribute() if (option.hasAttribute){ optValue = (option.hasAttribute("value") ? option.value : option.text); } else { optValue = (option.attributes["value"].specified ? option.value : option.text); } //將鍵和值分別進行編碼並用'='連接起來 parts.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(optValue)); } } } break; case undefined: //fieldset case "file": //file input case "submit": //submit button case "reset": //reset button case "button": //custom button break; case "radio": //radio button case "checkbox": //checkbox //如果沒有選中項 if (!field.checked){ break; } default: if (field.name.length){ parts.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(field.value)); } } } return parts.join("&"); }
<form name="form" action="test.php"> <input type="text" value="123" name="text"> <select name="color" multiple> <option>紅</option> <option>綠</option> <option>藍</option> </select> <input type="radio" name="gender" id="male" value="male"><label for="male">男</label> <input type="radio" name="gender" id="female" value="female"><label for="female">女</label> <input type="checkbox" name="time" value="12" id="t12"><label for="t12">12小時</label> <input type="checkbox" name="time" value="24" id="t24"><label for="t24">24小時</label> <button name="btn" type="button">按鈕</button> </form> <div id="result"></div> <script> var oForm = document.forms.form; oForm.onchange = function(e){ e = e || event; result.innerHTML = serialize(form); } </script>
FormData
當HTML表單同時包含文件上傳元素和其他元素時,瀏覽器不能使用普通的表單編碼而必須使用稱為“multipart/form-data”的特殊Content-Type來用POST方法提交表單。這種編碼包括使用長“邊界”字符串把請求主體分離成多個部分。對於文本數據,手動創建“multipart/form-data”請求主體是可能的,但很復雜
XMLHttpRequest 2級為此定義了FormData類型。FormData為序列化表單以及創建與表單格式相同的數據(用於通過XHR傳輸)提供了便利
[注意]IE9-瀏覽器不支持
【構造函數】
new FormData (form? : HTMLFormElement)
可選參數form表示一個HTML表單元素,可以包含任何形式的表單控件,包括文件輸入框
【append()】
append()方法用於給當前FormData對象添加一個鍵/值對
void append(DOMString name, Blob value, optional DOMString filename); void append(DOMString name, DOMString value);
參數值name表示字段名稱;參數值value表示字段值;參數值filename(可選)用於指定文件的文件名,當value參數被指定為一個Blob對象或者一個File對象時,該文件名會被發送到服務器上,對於Blob對象來說,這個值默認為"blob"
【其他不常用方法】
get():通過get(key)/getAll(key)來獲取對應的value
set():通過set(key,value)修改數據,如果指定的key不存在則新增一條,如果存在,則修改對應的value值
has():通過has(key)來判斷是否對應的key值
delete():通過delete(key)來刪除數據
[注意]以上4個不常用方法,IE瀏覽器都不支持
var oData1 = new FormData(); console.log(oData1.has('a'));//false oData1.append('a',1); console.log(oData1.has('a'));//true console.log(oData1.get('a'));//1 oData1.set('a',2); oData1.append('a',1); console.log(oData1.get('a'));//2 console.log(oData1.getAll('a'));//["2", "1"] oData1.delete('a'); console.log(oData1.get('a'));//null
<form action="#" name="form1"> <input type="text" value="1" name="a"> </form> <script> var oData1 = new FormData(document.forms.form1); console.log(oData1.has('a'));//true console.log(oData1.get('a'));//1 oData1.append('a',2); console.log(oData1.get('a'));//1 console.log(oData1.getAll('a'));//['1','2'] </script>
一般地,我們使用FormData()構造函數創建FormData對象,然后按需多次調用這個對象的append()方法把個體“部分”(可以是字符串、File或Blob對象)添加到請求中。最后,把FormData對象傳遞給send()方法,通過XHR對象將其發送到服務器
[注意]multipart/form-data只能用於post方式
<form action="#" name="form1"> <select name="a"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> </form> <div id="result"></div> <script> var oForm = document.forms.form1; oForm.onchange = function(){ //創建xhr對象 var xhr; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Microsoft.XMLHTTP'); } //異步接受響應 xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if(xhr.status == 200){ //實際操作 result.innerHTML = xhr.responseText; } } } //發送請求 xhr.open('post','t1.php' ,true); xhr.send(new FormData(form1)); } </script>