標籤:style blog http io color ar os 使用 sp
標題裡的技術都是web開發裡最常見的技術,但是我想這些常用的技術有很多細節是很多朋友不太清楚的,理解這些細節是我們深入掌握這些技術的一把鑰匙,今天我就講講我使用這些技術時體會到的這些細節。
同步提交是指通過對頁面的form表單執行submit操作,將使用者在頁面上錄入的資料提交到伺服器,伺服器處理完資料後將結果資訊返回到頁面上。
當我們使用同步提交的時候有一個不可或缺的元素,就是form標籤,form標籤代表了一個邊界,在form範圍內的input,select等元素,在form執行submit事件後,它們所記錄的資料就會被瀏覽器使用http協議以post或者get的方式傳輸到對應的伺服器上。
那麼我們如何執行form的submit操作了?瀏覽器為我們提供了四種方式:
方式一:
<input type=”submit” value=”Submit”/>
方式二:
<button type=”submit”>Submit</button>
方式三:
<input type=”image” src=”submit.gif”/>
方式四:
<form id=”frm” name=”frm”></form>document.getElementById(“frm”).submit()
這裡最特別的是方式三,在一個form標籤裡包含一個type為image的元素,其效果和一個submit按鈕居然一樣,這個估計很多人沒有發現。
不過從我多年的使用感受來說,前三種方式的本質是一樣,應該歸為一類,而第四種方式靈活性最高,也是最常用的,在實際開發裡我們最好只使用第四種方式,摒棄前三種方式,為了讓我的建議由說服力,我下面就講講前三種方式的壞處。
壞處一:form表單會自動認可。
form的自動認可一般都是我們在頁面錄入資訊時候,不小心按了斷行符號鍵,具體點是當form表單下的輸入元素(例如:文字框、密碼框、下拉框、單選框等等,不過textarea文本域除外)擷取焦點,這個時候你按了斷行符號鍵就會導致form表單自動認可。為了防止這種情況的發生, 我們會這麼改寫form,如下所示:
<form name="form1" method="post" onsubmit="return false;">
onsubmit="return false;"就可以讓我們阻止form表單自動認可行為,雖然我們有瞭解決這種問題的手段,但是我們並不知道什麼原因促使了這個問題的發生,其實問題的根本原因就是我們開發的頁面裡有前三種方式中的一種方式,這幾個特別的用於提交form表單的元素是問題的罪魁禍首。
壞處二:當使用者在頁面填寫完資料後,使用者接著要執行submit操作,當今的網站幾乎都不會讓使用者就這麼直接把資料提交到後台,出於各種要命原因的考慮,我們必須在瀏覽器提交資料前對資料進行校正或者對某些資料進行格式的轉化。
由於上面的原因,我們就不得不在有submit能力的標籤上添加校正方法,這些方法往往是和點擊事件綁定在一起,這似乎沒啥問題,但有時我們卻發現頁面在測試的時候居然會不通過,不通過的原因是我們探索服務端接收到了兩次同樣的http請求,我發現很多做了多年開發的朋友對這個問題的原因也是不甚明了,可能這個問題的解決方案就和上面的壞處一提供的解決方案一樣,太完美了,完美的阻止了我們探求真相的原因,這個問題的解決方案就是:將按鈕的類型變成button,同時不要使用form表單裡附帶了submit功能的按鈕,而是方式四來提交form表單。
使用具有submit功能元素提交form表單會有會有重複提交的原因是這些元素本身有一個預設的點擊事件,這個點擊事件是用來執行submit操作,如果我們給submit按鈕添加校正方式時候也是使用點擊事件,如果我們代碼裡不小心寫了個form.submit,那麼結果就會讓form表單執行兩遍。當然在點擊事件裡有辦法阻止預設事件就是:evt.preventDefault(),這樣按鈕就不會執行預設的submit事件,關於這一點在我們使用a標籤時候也會經常發生,所以我們會常常看到a標籤裡有這種寫法href="javascript:void(0)",其目的就是阻止a標籤執行預設的href屬性的點擊事件,同樣對於a標籤evt.preventDefault()也是有效,它也能阻止a標籤的預設事件。
方式四是使用javascript語言來控制form表單的提交,所以方式四和javascript代碼結合最好,而當今網站的網頁都會使用大量的javascript代碼來增強頁面的智能性,毫不猶豫的使用方式情節四做form的submit操作,現在應該要當做一種慣例來執行。
不過有很多情境只能使用form表單和服務端互動,這種獨佔的情境都是在和ajax技術進行比較後得出了,如果要解釋清楚原因,得先把ajax給聊清楚,所以這裡我們暫時先賣個關子,等到ajax技術講解完畢後,我們回過頭來在聊聊只能使用form表單的情境。
Ajax這個名字是個縮寫,它的全稱應該叫做非同步javascript+xml的技術,它的出現對web前端開發具有劃時代的意義,正式因為ajax,提升了瀏覽器在web應用中的作用,才需要更加專業的web前端工程師專門從事web前端的開發。
Ajax技術的核心是XMLHttpRequest對象, XMLHttpRequest對象是一個javascript內建對象,不過在ie6以下的版本(包括ie6)需要使用ActiveXObject方式建立它。關於XMLHttpRequest的用法我想只要做過ajax應用的人都十分熟悉和清楚,因此我這裡不想對XMLHttpRequest做過多描述,不過還是要貼一份代碼,這樣便於我接下來的講述。
代碼如下:
<script type="text/javascript">var xmlhttp;function loadXMLDoc(url){xmlhttp=null;if (window.XMLHttpRequest) {// code for all new browsers xmlhttp=new XMLHttpRequest(); }else if (window.ActiveXObject) {// code for IE5 and IE6 xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); }if (xmlhttp!=null) { xmlhttp.onreadystatechange=state_Change; xmlhttp.open("GET",url,true); xmlhttp.send(null); }else { alert("Your browser does not support XMLHTTP."); }}function state_Change(){if (xmlhttp.readyState==4) {// 4 = "loaded" if (xmlhttp.status==200) {// 200 = OK // ...our code here... } else { alert("Problem retrieving XML data"); } }}</script>
上面的例子是使用ajax執行get的請求,這個執行操作的代碼如下:
xmlhttp.onreadystatechange=state_Change; xmlhttp.open("GET",url,true); xmlhttp.send(null);
這三行代碼還是很有學問的,有些細節可能很多朋友理解的並不是太清楚,下面我就講講這些細節。
細節一:open方法,open方法有三個參數,第一個參數是http請求的方式,XMLHttpRequest常用的請求方式有兩種:get和post,第二個參數是請求的url,如果你使用XMLHttpRequest對象執行get操作,那麼你就得把參數組織在url裡,第三個參數的含義是【是否非同步發送請求】,第三個參數的解釋我用括弧括起來,想表達的就是第三個參數其實很多朋友並沒有正確理解它,該參數可以傳兩個值:true和false,true代表非同步發送請求,false代表同步發送請求,那麼這裡就有一個問題什麼叫做非同步發送請求,什麼叫做同步發送請求了?其實XMLHttpRequest裡非同步和同步的含義是指ajax執行時候是否阻塞後續javascript代碼的執行,如果我們選擇同步執行方式,那麼ajax執行http請求後,其他javascript代碼必須等待http執行完畢即請求得到了響應並且響應結果被處理完畢後,其他的javascript代碼才能執行,如果我們選擇非同步執行方式,那麼ajax執行http請求後不會阻塞其他javascript代碼執行即ajax執行http請求後瀏覽器會單獨起一個線程等到這個http的響應,當前線程馬上就可以執行其他javascript代碼,等獨立線程獲得了http響應再去插隊UI線程執行ajax的回呼函數。這也是我前面文章講到使用ajax方式解決阻塞指令碼問題的原理。
細節二:關於send方法,send方法的參數只有一個即send(body),在使用get方式提交請求,send方法傳入null值,如果我們使用post方式提交我們會將參數放置在send方法的參數裡,對於這一點區別很多朋友都不是很清楚,因為我們做web開發時候不管是get方式還是post方式都會傳遞參數,為什麼到了ajax會單獨用個send方法傳遞參數呢?解釋這個原因就得從http協議說起,我們知道http的get請求的參數都是放置到url後面,而post則不會,其實這種不同不僅僅源自於我們看到的現象,到了http報文的組織也不一樣,get請求參數是和url整合的,而http報文記錄url的位置是http頭,而執行post請求的時候,http報文組織時候會將url和請求參數分離,url還是放置在http頭部,請求參數則是作為報文主體的一部分發送至伺服器,而send方法的一個作用就是可以往http報文的body即主體裡填寫請求參數,因此get的請求就無法使用send傳遞參數,因為get不需要http的主體裡傳輸參數,只有post方式可以使用send方法傳遞參數。由此可見XMLHttpRequest對象給開發人員提供了更多精確控制http請求的能力,這一點是傳統form表單提交方式所不能給予的。
細節三:本細節也是關於send方法的問題,不過這個細節和post請求方式聯絡更加緊密,所以這裡我先給出一種post請求的寫法:
xmlhttp.open("POST",url,true); //post請求要自己佈建要求頭 xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); //發送資料,開始與伺服器進行互動 //post發送請求 xmlhttp.send("name=sharpxiajun&city=shanghai ");
一般我們使用post請求提交時候,要設定http的頭Content-Type為application/x-www-form-urlencoded,而send的請求參數的組織方式就是按照get請求封裝到url後面參數的形式來的。為什麼ajax要這麼做了?
我記得幾年前我曾請教過一位朋友關於這個問題的答案,他告訴我ajax的post請求是類比form表單執行post請求,而form表單使用post提交的時候會將http要求標頭裡的Content-Type置為application/x-www-form-urlencoded,當時他還寫了一個例子來說明這個問題。雖然問題得到瞭解答,但是卻讓我感覺到XMLHttpRequest更加神秘,為什麼ajax做post請求要類比同步的form表單的post請求了?它自己做個屬於自己的post請求方式不行嗎?而且更可惡的是一直到前不久我都覺得ajax沒有提供非類比form表單提交的方式。
這種迷惑的根源在哪裡了?首先我們思考下在沒有ajax的情況下,我們如何執行post操作,答案只有一個就是使用form表單,除此之外別無他法,相比之下get就不同的,get方式的請求是將url和參數融為一體,例如下面寫法:
www.cnblogs.com? name=sharpxiajun&city=shanghai
而javascript的內建對象有很多操作url的方式,最典型的一種就是:
location.href = url
如果我們在url後面加入參數,參數任然可以被服務端所擷取,但是這個過程就沒有使用form表單了。
在同步提交的時代下瀏覽器要執行post請求只能通過form,但是這個form對post的處理還不僅僅這些,每當我們使用post提交form時候會將Content-Type置為application/x-www-form-urlencoded,這個參數的作用僅僅是讓接收伺服器知道這個請求使用了post方式提交資料的嗎?
我們知道http使用get方式傳參,參數會有長度的限制,這主要是url最大長度制約了參數的長度,但是使用post則不同,post將參數放置在http請求體裡,原則上是沒有任何長度限制。想想我們經常使用瀏覽器下載圖片,上傳圖片,甚至還能使用瀏覽器上傳下載超大的視頻檔案,這些都是歸功於請求資料放置在http檔案體裡,因此使用post的http請求我們可以傳輸任何資料,資料格式不僅僅局限於文本,那麼如果有一天我們要使用form表單傳輸一個檔案,檔案在網路裡會被傳化為二進位,等資料到了服務端後還需要轉碼,還原檔案,這些操作都只能在post請求方式下進行,所以post傳參比get複雜的多,為了讓服務端知道這個複雜的情形,我們就必須要告訴伺服器會有這種情況發生,所以form做post請求就會出現指定Content-Type的問題,伺服器接收到這個Content-Type就明白了,請求資料的情況會很複雜,要注意了。
到了ajax,我們也需要執行post請求,但是ajax的post請求方式是類比form表單的post方式,這麼做的目的是為了照顧伺服器的感情,本來頁面的post提交情況對於服務端就很複雜了,要是ajax另起爐灶使用新方式提交post資料,那麼不是所有的web伺服器都要版本升級,這樣的結果就是低版本的web容器就不能享受ajax的便利了,這麼做的壞處也就不言而喻了,所以我們希望web伺服器能一視同仁的對待所有post請求。
不過,XMLHttpRequest操作ajax請求真的只有類比form表單提交方式這一種嗎?答案還真不是,其實使用ajax做post請求我們可以完全沒必要使用類比form表單提交方式,我前不久開發時候就碰到了這個問題,我開發一個新系統時候為了保證系統的安全性,提出一個要求:所有頁面提交到服務端的請求如果包含請求參數都得把Content-Type置為application/json,如果不是application/json請求,就會被拒,聽人說這是系統規範,考,哪來這麼操蛋的規範啊,如是我開發時候就改了Content-Type,結果是服務端居然接收不到請求參數了,但是通過firebug抓包,瀏覽器請求成功發送了,這下子頭大了,問題出在哪裡了?什麼地方導致請求資料丟失了?
最終我發現了問題原因,是send方法,使用send傳參時候我們都會這麼寫xmlhttp.send("name=sharpxiajun&city=shanghai ");名值對用=分割,不同名值對用&號分割,這個參數形式叫做序列化,在jQuery裡還專門有做序列化的方法,在使用form表單同步提交方式下,我們不用考慮參數序列化的事情,因為submit後瀏覽器會幫我們做好這件事情,不過同步提交下也有痕迹告訴瀏覽器做了序列化的操作,例如上面我講到的使用非form表單提交get請求的方式,但是使用ajax就得我們自己幹這些事情了,使用jQuery多的人常常忽視了序列化的背景知識,因為jQuery讓我們習慣了使用json格式提交資料,所以說要學好javascript基本功還是很重要的。序列化的資料到了服務端,web伺服器就會幫我們把名值對解析好,但是當我們改變了Content-Type值後,web伺服器就不會去做這件事情即參數沒有被解析,所以在java裡的servlet我們就不能按傳統的方式擷取參數了,但是參數其實並沒有消失,而是解析方式不對,所以最後我們這麼改寫ajax:
xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8;"); var _param = {}; xhr.send(JSON.stringify(_param));
send裡傳遞的不再是序列化資料,而是使用json格式的字串,這樣web伺服器就不是通過序列化解析資料,而是通過json解析了。
由此可見XMLHttpRequest給我們操作http請求帶來了更大的靈活性。
細節四:本細節任然是send方法的問題,send參數在官方文檔裡可以接受兩種類型的參數一種是xml,一種是字串,前面例子裡我們都是使用的字串,而沒有使用xml格式,本來我想實驗一下xml格式,可惜,我一直沒做這個事情,所以本篇文章拖了許久沒發表了,今天想想算了,不實驗了,讓看了本文的大牛們去試試。
前不久看了一篇文章,裡面寫到send的參數可以是form表單的DOM即可以這麼傳參數xhr.send(document.getElementById(“frm”)),這個做法我也沒實驗過,但是這個描述會影響到我下文的描述,如果我form表單裡包含檔案上傳的input,這麼做的post請求會將檔案資料提交到後台伺服器嗎?
現在我們拋開send非字串的參數類型,假設我們的XMLHttpRequest只能傳遞字串,那麼問題來了我們就沒法使用ajax提交檔案資料到伺服器了,換句話說XMLHttpRequest是無法上傳檔案的。我很早之前寫了一個ajax檔案上傳的架構,使用hack的技術類比非同步檔案上傳操作,它的原理是在首頁面構造一個隱藏的form表單和隱藏的iframe,我將頁面可見form裡的元素拷貝到隱藏的form裡,然後submit這個form,隱藏的form請求響應由隱藏的iframe接收,然後通過操作iframe的響應資料來完成對ajax非同步檔案上傳類比。
hack技術寫起來很爽很有成就感,但是如果有天然的ajax非同步提交方式才是最適合生產開發的,本來細節四是為了指出ajax不能做檔案上傳操作,但是查閱資料後發現可能是我沒有找到正確途徑,這個需要我更進一步研究,因為自己還沒有實踐,但是為了保持內容的嚴謹,我還是要提提自己的質疑,如果結果是XMLHttpRequest的確不能這麼做,那麼我也給出了類比非同步檔案請求的方式,一舉兩得,其實有時沒有答案的思考也能給人以長進。
細節五:ajax的x本意是xml,隨著ajax技術的發展,我們在ajax裡使用xml其實並不太多了,取代xml的技術是更好用的json,為什麼有這個轉變了?
上文我講到send方法還有個參數類型就是xml,但是實際上我們都快遺忘它有這個參數,因為瀏覽器裡將一段和xml相似的字串轉化為真正的xml並不太容易,而且不同瀏覽器構造xml還存在差異,這點可能有些朋友看到了會不太理解,因為很多人都知道html是xml的子集,我這裡居然會大放厥詞說javascript和xml居然融合的不好,但事實就是如此,javascript裡產生xml是個冷僻技術了,雖然javascript規範裡的確規定了產生xml方式,但是實際開發裡真正使用的少之又少,這裡要註明下,本段產生xml指的是使用javascript構造xml,不是使用javascript解析xml。
當伺服器返回的資料是xml時候,javascript操作xml就變的簡單些了,我們可以直接將xml傳化為dom操作,這個操作和操作html類似。不過操作xml即操作dom沒有json好,下面我們看看下面的例子:
<user> <name>sharpxiajun</name> <city>shanghai</city></user>
如果我們用dom操作這個xml想取到name的值,我們就得遍曆,上面的例子很簡單,如果再複雜點即name下還有子項目,那麼這種遍曆就更深,程式編寫也就越複雜,不過我們有個替代方法,看下面的xml:
<user name="sharpxiajun" city="shanghai"></user>
這個時候我們使用XXX.getAttribute(“name”)就能擷取name的值,而且這個xml比上面變簡單了,同時資料也變小了。但是這也說明了另外一個問題,就是xml太靈活了,同一個資料可以選擇多種多樣的方式表示,這樣的結果是xml表現資料很不直觀,同時傳輸的資料量也很大,而且傳輸的資料裡有很多內容是為了用於表達資料含義的,而不是真正有用的資料。相比之下,json格式的資料就比較單純,例如下面的json:
{"name":"sharpxiajun","city":"shanghai"}
這段json我們能很清晰的看懂,而且json很容易傳化為javascript對象,操作十分方便,所以json最終取代了xml。
談到json,我要插一句,記得有個網友曾經私下問我一個問題,說他一次做程式碼檢閱時候一個年輕的程式員跟他提了個問題,因為問我的朋友在執行ajax操作時候喜歡使用json傳遞資料(這位朋友一般用jQuery),所以每次構造參數他都喜歡這麼寫:
var param = {name:"sharpxiajun",city:"shanghai"}
年輕的程式員認為他的寫法不規範,因為json的規範裡不管是名值對的名還是值都要加引號(幸虧這個程式員說是引號,沒有排除單引號),那麼他認為上面的寫法會有安全隱患,隱患的原因就是不規範,但是問我的朋友說他開發做了好幾年,幾乎都是這麼寫的,沒發現有問題,當時他問我,我也被問倒了。
但是我現在知道答案了,json是什嗎?json的定義是javascript對象的表示方法,它是一種資料格式,json不是變數。所以json其實不是javascript對象,它是按照javascript對象字面量的模式定義的資料格式,這個資料格式可以被服務端語言解析為map對象。其實問我的朋友這麼寫一點問題都沒有,這個朋友的寫法是javascript對象的字面量定義方式,javascript對象的名值對的名可以不用引號,如果你傳入參數是javascript對象,程式一點隱患都沒有,不過網路傳輸時候,javascript對象要變成json格式,瀏覽器會讓名值對的名加上引號的。
細節六:ajax的核心對象XMLHttpRequest可以操作http的要求標頭.
例如:
xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8;");
這給ajax帶來了很大的靈活性,記得有個初學者曾問我過一個問題,XMLHttpRequest可以操作http要求標頭,那同步請求怎麼定義要求標頭呢?我當時也被這個問題卡殼了,同步請求我們可以操作要求標頭嗎?我當時回答是不行,其實同步提交請求可以改變http的要求標頭,那就是html的meta標籤,如所示:
相比之下XMLHttpRequest對要求標頭的操作更加靈活。
哦,十一點半了,寫不完了,只得分篇寫了,下一篇文章我會繼續講解ajax技術,然後把同步提交和ajax技術作對比,接下來講comet(伺服器推送技術),最後來個大總結。也許分篇寫,我還能記起更多能容哦。
探求網頁同步提交、ajax和comet不為人知的秘密(上篇)