深入理解ajax系列第四篇
前面的话
现代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>

深入理解ajax系列第四篇的更多相关文章
- 深入理解ajax系列第四篇——请求实例
前面的话 在使用ajax的过程中,常用的请求方式是GET和POST两种.本文将以实例的形式来详细说明这两种请求方式 GET GET是最常见的请求类型,最常用于向服务器查询某些信息.必要时,可以将查询字 ...
- 深入理解ajax系列第四篇——FormData
前面的话 现代Web应用中频繁使用的一项功能就是表单数据的序列化,XMLHttpRequest 2级为此定义了FormData类型.FormData为序列化表单以及创建与表单格式相同的数据提供了便利. ...
- 深入理解ajax系列第八篇——表单提交
前面的话 在以前,网站的用户与后端交互的主要方式是通过HTML表单的使用.表单的引入在1993年,由于其简单性和易用性,直到电子商务出现之前一直保持着重要位置.理解表单提交,对于更深入地理解ajax是 ...
- 深入理解ajax系列第八篇
前面的话 在以前,网站的用户与后端交互的主要方式是通过HTML表单的使用.表单的引入在1993年,由于其简单性和易用性,直到电子商务出现之前一直保持着重要位置.理解表单提交,对于更深入地理解ajax是 ...
- 深入理解ajax系列第三篇——响应解码
前面的话 我们接收到的响应主体类型可以是多种形式的,包括字符串String.ArrayBuffer对象.二进制Blob对象.JSON对象.javascirpt文件及表示XML文档的Document对象 ...
- 深入理解ajax系列第七篇——传递JSON
前面的话 虽然ajax全称是asynchronous javascript and XML.但目前使用ajax技术时,传递JSON已经成为事实上的标准.因为相较于XML而言,JSON简单且方便.本文将 ...
- 深入理解ajax系列第七篇
前面的话 虽然ajax全称是asynchronous javascript and XML.但目前使用ajax技术时,传递JSON已经成为事实上的标准.因为相较于XML而言,JSON简单且方便.本文将 ...
- 深入理解ajax系列第三篇
前面的话 我们接收到的响应主体类型可以是多种形式的,包括字符串String.ArrayBuffer对象.二进制Blob对象.JSON对象.javascirpt文件及表示XML文档的Document对象 ...
- 深入理解ajax系列第三篇——头部信息
前面的话 每个HTTP请求和响应都会带有相应的头部信息,其中有的对开发人员有用.XHR对象提供了操作头部信息的方法.本文将详细介绍HTTP的头部信息 默认信息 默认情况下,在发送XHR请求的同时,还会 ...
随机推荐
- 20155234 2016-2017-2 《Java程序设计》第6周学习总结
20155234 2016-2017-2 <Java程序设计>第6周学习总结 教材学习内容总结 Java将输入/输出抽象化为串流,数据有来源及目的地,衔接两者的是串流对象. 从应用程序角度 ...
- 1.phpcms的安装和卸载文件
一.安装文件 ①languages文件夹 语言包:zh-cn\order.lang.php //样式<?php $LANG['order_new'] = '订单管理'; ?> ②templ ...
- 45、文件过滤器FilenameFilter
文件过滤器FilenameFilter JDK中提供了一个FilenameFilter的接口用来实现文件过滤功能,可以使用这个文件过滤器来实现上一节中的问题. File类中有一个带参数的list方法 ...
- 苹果手机浏览器$(document).on(“click”,function(){})点击无效的问题
<label class="js_highlight" style="display: inline-block;float: left;width: 50%;&q ...
- D. Makoto and a Blackboard(积性函数+DP)
题目链接:http://codeforces.com/contest/1097/problem/D 题目大意:给你n和k,每一次可以选取n的因子代替n,然后问你k次操作之后,每个因子的期望. 具体思路 ...
- 假·最大子段和 (sdutoj 4359 首尾相连)(思维)
题目链接:http://acm.sdut.edu.cn/onlinejudge2/index.php/Home/Contest/contestproblem/cid/2736/pid/4359 具体思 ...
- 如何更方便的查看Linux内核代码的更新记录【转】
转自:http://blog.csdn.net/lee244868149/article/details/44302819 Linux内核的更新非常的快,如何快速的了解这些更新呢?最一般的办法就是把新 ...
- c# CTS 基础数据类型笔记
C#中的基础数据类型并没有内置于c#语言中,而内置于.net freamework. C#有15个预定义类型,其中13个是值类型,两个是引用类型(string和object) 一.值类型 值类型 数据 ...
- 使用脚本实现killproc的功能
在shell提示符号下输入type killproc,会发现killproc实在 /sbin/目录下,通过man killproc可以查看这个脚本(姑且这么称为脚本)的用法,现在,把这个脚本的实现过程 ...
- 线性表应用--Josephus问题的解法(Python 版)
线性表应用 --Josephus问题的解法(Python 版) Josephus问题描述:假设有n个人围坐一圈,现在要求从第k个人开始报数,报到第m个数的人退出.然后从下一个人开始继续报数并按照相同的 ...