前言
今天想要講的東西比較雜亂,自己理了好久的思路感覺一直找不到一條線串聯(lián)起這些碎片化的知識(shí)。然后就想著那就先寫寫看吧,寫到哪算哪,最后再調(diào)整調(diào)整。所以童鞋們看的時(shí)候就不要太在意邏輯哈。
1、從form表單提交說起
為什么從表單提交說起呢?因?yàn)榇蟛糠峙c后臺(tái)的交互都是在form表單中實(shí)現(xiàn),恰巧我入職一個(gè)月來都是在處理與后臺(tái)交互的數(shù)據(jù)整合中度過,期間也發(fā)現(xiàn)一些小坑,出于喜歡總結(jié),所以才想寫這篇小博客。
各位童鞋,可以先看一下這個(gè)例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>testing form group</title>
<script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.0/jquery.min.js"></script>
<script type="text/javascript">
function onSubmit(){
var finalRes = $('#form').serializeArray().reduce(function(result, item){
result[item.name] = item.value;
return result;
}, {})
}
</script>
</head>
<body>
<form action="" method="post" name='info' id='form'>
<input type="text" name="user" />
<input type="text" name="email" />
</form>
<button type="button" name='submit' onclick="onSubmit()">提交</button>
</body>
</html>
Tips:
對(duì)于form表單有兩種提交方法:
- 上面的使用button來收集表單信息然后校驗(yàn)并提交到服務(wù)器
- 直接在form表單中添加
type=submit屬性的input或者button類型的按鈕,點(diǎn)擊按鈕后表單根據(jù)action和method的配置直接提交表單。
對(duì)于后一種提交表單的方式還有很多配置選項(xiàng),包括enctype、accept和accept-charset。
當(dāng)然上面可以配置的屬性在我們即將要講的Jquery的ajax方法中都是可以配置的。所以我們先說說上面代碼中涉及到的幾個(gè)知識(shí)點(diǎn)。
1.1、type="submit" vs type="button"
type="submit"是將表單提交(即form.submit()方法)作為其onclick后的默認(rèn)事件。type="submit"會(huì)自動(dòng)將所有具有name屬性的html輸入元素(包括input標(biāo)簽、button標(biāo)簽、select標(biāo)簽等)都將作為鍵值對(duì)提交。type="submit"submit會(huì)有一個(gè)跳轉(zhuǎn),頁面會(huì)刷新;
上面的3點(diǎn)對(duì)于type="button"統(tǒng)統(tǒng)都沒有,type="button"只是一個(gè)單純的點(diǎn)擊,點(diǎn)擊事件需要你自己添加,就如上面的代碼那樣。
如果一切都是你說的那樣:type="submit"會(huì)自動(dòng)提交所有的表單信息,那么我們?nèi)绾涡r?yàn)參數(shù)呢?這時(shí)候便是使用表單事件處理函數(shù)onSubmit,你可以在函數(shù)內(nèi)部校驗(yàn)參數(shù),不成功就返回false,這樣就可以阻止表單的提交了。
另外該事件處理函數(shù)不是肯定會(huì)被調(diào)用的,條件如下:
The submit event is raised when the user clicks a submit button in a form (<input type="submit"/>).
The submit event is not raised when the user calls form.submit() function directly.
1.2、.serializeArray()
這個(gè)是Jquery的方法,目的是為了搜集表單元素內(nèi)部所有可以搜集的標(biāo)簽的name和value,然后組合成類似這種形式的對(duì)象值:{name:"xxx",value:"xxx"}。于是如果表單中有多個(gè)輸入選項(xiàng)那么結(jié)果應(yīng)該是這樣的:
[{name:"xxx",value:"xxx"},{name:"xxx",value:"xxx"}....]
1.3、reduce()
這個(gè)是ES6新加的語法,具體用法可以參考MDN。
然后我們?cè)谶@里使用它是為了將剛才序列化之后的值轉(zhuǎn)變?yōu)镴son數(shù)據(jù),以上面的代碼為例子其結(jié)果應(yīng)該是這樣的:
{user:"xxxx",email:"xxxxxx"}
這個(gè)時(shí)候這個(gè)結(jié)果便是我們想要提交給后臺(tái)服務(wù)器的數(shù)據(jù),那么問題來了,我們是可以直接將這個(gè)數(shù)據(jù)提交給服務(wù)器嗎?中間還需要做些什么嗎?
2、使用$.ajax()
用過Jquery的童鞋肯定都用過$.ajax()這個(gè)函數(shù),其最普通的用法便是:
$.ajax({
type: 'POST',
url: ,
data: ,
dataType: 'json',
success: function(data){successCallback(data)},
error: function(jqXHR){failureCallback(jqXHR)},
})
那么問題來了:你該如何配置正確的參數(shù)才能讓你的前后臺(tái)能夠完美的協(xié)作呢?
2.1、ajax方法中幾個(gè)重要的參數(shù)
2.1.1. contentType
默認(rèn)值為(application/x-www-form-urlencoded; charset=UTF-8)。根據(jù)Jquery的API文檔我們知道:
當(dāng)將數(shù)據(jù)發(fā)送到服務(wù)器時(shí),使用該內(nèi)容類型(或者叫編碼類型)。默認(rèn)值是"application/x-www-form-urlencoded; charset=UTF-8",適合大多數(shù)情況。如果你明確地傳遞了一個(gè)內(nèi)容類型(Content-Type)給 $.ajax(),那么他總是會(huì)發(fā)送給服務(wù)器(即使沒有數(shù)據(jù)要發(fā)送)。從 jQuery 1.6 開始,你可以傳遞false來告訴jQuery,沒有設(shè)置任何內(nèi)容類型頭信息。 注意:W3C的XMLHttpRequest的規(guī)范規(guī)定,數(shù)據(jù)將總是使用UTF-8字符集傳遞給服務(wù)器;指定其他字符集無法強(qiáng)制瀏覽器更改編碼。 注意:對(duì)于跨域請(qǐng)求,內(nèi)容類型設(shè)置為application/x-www-form-urlencoded, multipart/form-data, 或 text/plain以外, 將觸發(fā)瀏覽器發(fā)送一個(gè)預(yù)檢OPTIONS請(qǐng)求到服務(wù)器。
那么我們就想知道這個(gè)參數(shù)配置的值都是些什么東東呢?常見的編碼類型有:
application/x-www-form-urlencoded:窗體數(shù)據(jù)被編碼為名稱/值對(duì)。這是標(biāo)準(zhǔn)的編碼格式。 (表單默認(rèn)的提交數(shù)據(jù)的格式)
multipart/form-data:窗體數(shù)據(jù)被編碼為一條消息,頁上的每個(gè)控件對(duì)應(yīng)消息中的一個(gè)部分。 (上傳文件之時(shí)使用)
text/plain:窗體數(shù)據(jù)以純文本形式進(jìn)行編碼,其中不含任何控件或格式字符。
application/json:窗體數(shù)據(jù)以Json的數(shù)據(jù)格式來傳遞。(傳遞[{},{},{}]這種json數(shù)組格式)
2.1.2. data
發(fā)送到服務(wù)器的數(shù)據(jù)。它被轉(zhuǎn)換成一個(gè)查詢字符串,如果已經(jīng)是一個(gè)字符串的話就不會(huì)轉(zhuǎn)換。查詢字符串將被追加到GET請(qǐng)求的URL后面。參見 processData 選項(xiàng)說明,以防止這種自動(dòng)轉(zhuǎn)換。對(duì)象必須為"{鍵:值}"格式。如果這個(gè)參數(shù)是一個(gè)數(shù)組,jQuery會(huì)按照traditional 參數(shù)的值, 將自動(dòng)轉(zhuǎn)化為一個(gè)同名的多值查詢字符串。
補(bǔ)充一點(diǎn)就是即使是在對(duì)象里的key/value中value是數(shù)組,也會(huì)自動(dòng)轉(zhuǎn)換成一個(gè)同名多址的字符串。比如將上面例子的代碼修改成:
<script type="text/javascript">
function onSubmit(){
/*var finalRes = $('#form').serializeArray().reduce(function(result, item){
result[item.name] = item.value;
return result;
}, {})
console.log(finalRes)
*/
$.ajax({
type: 'POST',
url: 'http://blog.5udou.cn/test/first',
data: {name: 'linxiaowu', pass: '123456', weekDays: [1,2,3,4]},
dataType: 'json',
contentType: 'application/x-www-form-urlencoded',
success: function(data){console.log(data)},
error: function(jqXHR){console.log(jqXHR)},
})
}
</script>
那么瀏覽器將解析成這樣的效果:

2.1.3. dataType
從服務(wù)器返回你期望的數(shù)據(jù)類型。 如果沒有指定,jQuery將嘗試通過MIME類型的響應(yīng)信息來智能判斷(一個(gè)XML MIME類型就被識(shí)別為XML,在1.4中 JSON將生成一個(gè)JavaScript對(duì)象,在1.4中 script 將執(zhí)行該腳本,其他任何類型會(huì)返回一個(gè)字符串)。 可用的類型(以及結(jié)果作為第一個(gè)參數(shù)傳遞給成功回調(diào)函數(shù))有:
"xml": 返回 XML 文檔,可以通過 jQuery 處理。
"html": 返回純文本 HTML 文本;包含的script標(biāo)簽會(huì)在插入DOM時(shí)執(zhí)行。
"script": 把響應(yīng)的結(jié)果當(dāng)作 JavaScript 執(zhí)行,并將其當(dāng)作純文本返回。默認(rèn)情況下會(huì)通過在URL中附加查詢字符串變量 ,_=[TIMESTAMP], 禁用緩存結(jié)果,除非設(shè)置了cache參數(shù)為true。注意: 在遠(yuǎn)程請(qǐng)求時(shí)(不在同一個(gè)域下),所有POST請(qǐng)求都將轉(zhuǎn)為GET請(qǐng)求。(愚人碼頭注:因?yàn)閷⑹褂肈OM的script標(biāo)簽來加載)
"json":把響應(yīng)的結(jié)果當(dāng)作 JSON 執(zhí)行,并返回一個(gè)JavaScript對(duì)象。跨域"json" 請(qǐng)求轉(zhuǎn)換為"jsonp",除非該請(qǐng)求在其請(qǐng)求選項(xiàng)中設(shè)置了jsonp:false。JSON 數(shù)據(jù)以嚴(yán)格的方式解析; 任何畸形的JSON將被拒絕,并且拋出解析錯(cuò)誤信息。在jQuery1.9中,一個(gè)空響應(yīng)也將被拒絕;服務(wù)器應(yīng)該返回null或 {}響應(yīng)代替。(見json.org的更多信息,正確的JSON格式。)
"jsonp": 以 JSONP 的方式載入 JSON 數(shù)據(jù)塊。會(huì)自動(dòng)在所請(qǐng)求的URL最后添加"?callback=?"。默認(rèn)情況下會(huì)通過在URL中附加查詢字符串變量 ,_=[TIMESTAMP], 禁用緩存結(jié)果,除非設(shè)置了cache參數(shù)為true。
"text": 返回純文本字符串。
多個(gè)用空格分割的值:從 jQuery 1.5 開始, jQuery可以內(nèi)容類型(Content-Type)頭收到并轉(zhuǎn)換一個(gè)您需要的數(shù)據(jù)類型。例如,如果你想要一個(gè)文本響應(yīng)為XML處理,使用"text xml"數(shù)據(jù)類型。您也可以將一個(gè)JSONP的請(qǐng)求,以文本形式接受,并用jQuery以XML解析: "jsonp text xml"。同樣地可以使用"jsonp xml"簡(jiǎn)寫,首先會(huì)嘗試從 jsonp 到 xml 的轉(zhuǎn)換,如果轉(zhuǎn)換失敗,就先將 jsonp 轉(zhuǎn)換成 text, 然后再由 text 轉(zhuǎn)換成 xml。
2.2、不同的method組合不同的ContentType
我們?cè)谏厦娴拇a中添加這樣一段代碼:
$.ajax({
type: 'POST',
url: 'http://blog.5udou.cn/test/first',
data: finalRes,
dataType: 'json',
contentType: 'application/x-www-form-urlencoded',
success: function(data){console.log(data)},
error: function(jqXHR){console.log(jqXHR)},
})
幾個(gè)關(guān)鍵的參數(shù)都已經(jīng)配置,那么在method='POST'的時(shí)候,data部分是如何組裝進(jìn)請(qǐng)求中的呢?答案是:瀏覽器把form數(shù)據(jù)封裝到http body中,然后發(fā)送到server,如圖:

如果是methos='GET'的時(shí)候,則瀏覽器用x-www-form-urlencoded的編碼方式把form數(shù)據(jù)轉(zhuǎn)換成一個(gè)字串(name1=value1&name2=value2...),然后把這個(gè)字串a(chǎn)ppend到url后面,用?分割,加載這個(gè)新的url。如圖:

如果沒有type=file的控件,用默認(rèn)的application/x-www-form-urlencoded就可以了。 但是如果有type=file的話,就要用到multipart/form-data了。瀏覽器會(huì)把整個(gè)表單以控件為單位分割,并為每個(gè)部分加上Content-Disposition(form-data或者file),Content-Type(默認(rèn)為text/plain),name(控件name)等信息,并加上分割符(boundary)。
2.3、特殊的application/json
如果你想要傳遞一個(gè)json Array的話,使用application/x-www-form-urlencoded是不行的,代碼改成:
<script type="text/javascript">
function onSubmit(){
/*var finalRes = $('#form').serializeArray().reduce(function(result, item){
result[item.name] = item.value;
return result;
}, {})
console.log(finalRes)
*/
$.ajax({
type: 'POST',
url: 'http://blog.5udou.cn/test/first',
data: [{name: 'linxiaowu', pass: '123456'}, {name:'xiaomizha', pass:'123456'}],
dataType: 'json',
contentType: 'application/x-www-form-urlencoded',
success: function(data){console.log(data)},
error: function(jqXHR){console.log(jqXHR)},
})
}
</script>
結(jié)果如下:

于是我們需要修改成這樣:
<script type="text/javascript">
function onSubmit(){
/*var finalRes = $('#form').serializeArray().reduce(function(result, item){
result[item.name] = item.value;
return result;
}, {})
console.log(finalRes)
*/
$.ajax({
type: 'POST',
url: 'http://blog.5udou.cn/test/first',
data: [{name: 'linxiaowu', pass: '123456'}, {name:'xiaomizha', pass:'123456'}],
dataType: 'json',
contentType: 'application/json',
success: function(data){console.log(data)},
error: function(jqXHR){console.log(jqXHR)},
})
}
</script>
結(jié)果如下:

咦,竟然報(bào)錯(cuò)!并且data的數(shù)據(jù)并沒有加到request里面去,這是為什么?
其實(shí)控制臺(tái)的錯(cuò)誤在上面的例子都是有報(bào)錯(cuò)的,關(guān)鍵在于報(bào)錯(cuò)的順序,為什么這么說呢?根據(jù)MDN的說明,我們發(fā)現(xiàn):
- 如果使用的方法不是POST或者GET,或者使用了POST方法但是Content-Type的配置不是application/x-www-form-urlencoded, multipart/form-data, or text/plain中的任意一個(gè)
- 如果在請(qǐng)求中設(shè)置了自定義的頭部(比如請(qǐng)求使用了類似X-PINGOTHER的頭部)
上面兩種情況都會(huì)執(zhí)行preflighted request,也就是執(zhí)行發(fā)送請(qǐng)求前的檢查:"preflighted"請(qǐng)求首先會(huì)發(fā)送一個(gè)HTTP OPTIONS的請(qǐng)求頭部到另外一個(gè)域下,這是為了決定實(shí)際的請(qǐng)求是不是可以安全地發(fā)送。
所以你在上圖中看到紅色框圈起來的METHOD為OPTIONS就是因?yàn)闄z測(cè)到你跨域了并且是使用的application/json + POST的請(qǐng)求方法,所以才會(huì)出現(xiàn)剛才的情況,找不到發(fā)送的數(shù)據(jù),請(qǐng)求方法也是不對(duì)的。
那么針對(duì)這種情況,我們修改請(qǐng)求的URL為同源的即可,這里在本地假設(shè)一個(gè)Express服務(wù)器,于是有:

咦?還是有錯(cuò)誤。這個(gè)時(shí)候就需要JSON.stringify函數(shù)出場(chǎng)了:
function onSubmit(){
var data = JSON.stringify([{name: "linxiaowu", pass: "123456"}, {name:"xiaomizha", pass:"123456"}])
$.ajax({
type: 'POST',
url: '/test/first',
data: data,
dataType: 'json',
contentType: 'application/json',
success: function(data){console.log(data)},
error: function(jqXHR){console.log(jqXHR)},
})
}
這個(gè)時(shí)候結(jié)果就是我們想要的:

2.3.1、Tips
JSON.stringify turns a Javascript object into JSON text and stores that JSON text in a string.
JSON.parse turns a string of JSON text into a Javascript object.
2.3.2、問題
留給童鞋們一個(gè)問題:在上面的例子代碼中直接將data的值賦給data,即:
$.ajax({
type: 'POST',
url: '/test/first',
data: [{"name":"linxiaowu","pass":"123456"},{"name":"xiaomizha","pass":"123456"}],
dataType: 'json',
contentType: 'application/json',
success: function(data){console.log(data)},
error: function(jqXHR){console.log(jqXHR)},
})
結(jié)果是錯(cuò)誤的,識(shí)別不到這是一個(gè)JSON,這是為什么呢??
3、服務(wù)器的反應(yīng)呢?
服務(wù)器這邊的處理比較簡(jiǎn)單,可以直接判斷請(qǐng)求的類型,如果不是想要地編碼類型可以直接回應(yīng)415(Unsupported Media Type),如果符合要求那么服務(wù)器將根據(jù)前后端約定的來獲取請(qǐng)求的參數(shù),以express為例子,獲取參數(shù)有三種方法:官網(wǎng)介紹如下:
- Checks route params (req.params), ex: /user/:id
- Checks query string params (req.query), ex: ?id=12
- Checks urlencoded body params (req.body), ex: id=
分別舉個(gè)例子:
- 例如:127.0.0.1:3000/index,這種情況下,我們?yōu)榱说玫絠ndex,我們可以通過使用req.params得到,通過這種方法我們就可以很好的處理Node中的路由處理問題,同時(shí)利用這點(diǎn)可以非常方便的實(shí)現(xiàn)MVC模式;
- 例如:127.0.0.1:3000/index?id=12,這種情況下,這種方式是獲取客戶端get方式傳遞過來的值,通過使用req.query.id就可以獲得,類似于PHP的get方法;
- 例如:127.0.0.1:300/index,然后post了一個(gè)id=2的值,這種方式是獲取客戶端post過來的數(shù)據(jù),可以通過req.body.id獲取,類似于PHP的post方法;
4、后記
啊哈~~~最后寫下來貌似還是有點(diǎn)邏輯的哈??磥砦恼掠械臅r(shí)候還是需要在寫的時(shí)候慢慢理清邏輯,貌似我get到了什么新技能。。。。
參考文章
- http://api.jquery.com/jQuery.ajax/
- http://www.css88.com/jqapi-1.9/jQuery.ajax/
- http://tool.chinaz.com/pagestatus/
最后
歡迎訪問我的個(gè)人博客主頁:豆米的博客