深刻研究某項技術,瞭解使用這些技術的細節,其實最終目的都是爲了完成一個選擇問題:當咱們要使用這些技術解決某個具體的問題時候咱們到底該如何去選擇。若是碰到有兩種技術可讓咱們達到一樣的目的,咱們就會不天然的去比較它們之間的差別,經過對這些差別的梳理,咱們就能得出在使用它們時候咱們到底該如何取捨。javascript
上篇裏我講到XMLHttpRequest能夠更加精確的控制http請求的報文頭,如是乎我就去尋找在同步提交裏我是否能像XMLHttpRequest那樣的去控制http的頭部信息呢,最終我發現同步提交下咱們能夠在html的head裏設置meta標籤,使用http-equiv來設置http的頭部信息,不過meta的頭部信息實際上是服務器給瀏覽器的響應頭部信息,而不是請求頭部信息,下面是咱們最經常使用,最能影響頁面展現的meta寫法,以下所示:html
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <meta http-equiv="expires" content="31 Dec 2099">
這個頭部是告訴瀏覽器該頁面是一個html文檔,字符集是UTF-8,還告訴瀏覽器你要將這個頁面緩存在瀏覽器裏,若是用戶不清理瀏覽器緩存,那麼該頁面在2099年12月31號前均可以從緩存讀取,不須要在從服務器下載整個html文檔了。前端
Expire是服務端指定瀏覽器的緩存行爲,在web前端優化裏它所表明的緩存能力是很是重要,ajax也能使用瀏覽器緩存,不過這塊知識須要和我後面要講的內容相關,因此這裏先暫時擱置一下。java
Content-type在上篇文章裏屢次出現,咱們開發頁面時候,html的head裏該屬性可謂是一個熟悉的陌生人,在不少用來開發頁面的IDE裏,咱們選擇模板建立一個頁面,這個標籤都是默認帶有的,都不用咱們本身添加它,可是我在多年開發經驗我還真的碰到過content-type使用不當所帶來的技術問題。node
很早前有位朋友請教我一個這樣的問題,他們公司有個web應用,這個web應用沒有太多的頁面,大部分是後臺程序,該系統的目的是對他們公司用戶提供一些特別的服務,用戶若是要使用該服務只須要在本身網站頁面裏跨域請求他們系統提供的網頁,爲了保證系統的安全,這位朋友的系統會對用戶發送的請求作不少安全性的檢查,這個檢查比較耗時,可是又很是重要,所以他們爲了給終端使用者以友好的用戶體驗,就在安全檢查正在處理時提供一個等待頁面,也許是由於這個朋友開發的系統服務端太重並且頁面太少太簡單,不是特別擅長頁面開發的他們開發時候就沒有單獨寫個等待頁面,而是將等待頁面放在後臺的servlet裏直接構造(其實就是在後臺直接拼寫頁面的字符串了),可是他們構造時候忘了在這個簡單頁面里加上content-type屬性,可惡的是這位朋友的系統測試時候沒有發現任何問題,可怕的是上了生產環境後某些調用他們系統的用戶在ie瀏覽器下頁面直接顯示了是源代碼而不是頁面,在非ie瀏覽器下倒是正常。不是全部人有問題,有問題也是部分瀏覽器,在本身的測試環境又模擬不出問題,這怎麼搞,那位朋友的項目組花了兩天都沒弄明白怎麼回事,而這時用戶的投訴是愈來愈多,如是他問我碰到過這種狀況嗎?當時我用ie和非ie抓包工具對比了在不一樣瀏覽器裏http請求的請求頭和響應頭,發現了區別:有的瀏覽器content-type是text/html,有的是text/plain,ie瀏覽器下都是text/plain,非ie兩種皆有,不過ie下若是是text/plain,那麼頁面就會顯示源碼,如是我把我看到的現象告訴了這位朋友,這位朋友查閱下代碼發現他們構造等待頁面時候沒有指定content-type頭,如是他們強制指定該頭爲text/html,問題就解決。雖然問題解決了,可是咱們仍是沒有找到什麼地方出問題了,爲何測試環境下不能重現問題的發生,這個現象對於作過互聯網開發的朋友可能就很好理解了,互聯網的應用部署的網絡環境是很是複雜的,用戶從瀏覽器發送一個http請求到服務端,這個過程並非像咱們平時開發那樣直接到像tomcat,Jboss這些web容器,而是會通過好多路徑,最多見的有cdn,負載均衡設備,靜態資源服務器例如apache等等,請求經過的這些環節都有權限和能力更改http報文,若是大家的網站規模越大,那麼網絡環境會更加複雜,幾乎很難找到一個知道這全部細節的人,因此在互聯網公司都會有一個預發佈環境,這個環境會盡最大努力模擬真實環境,雖然這個環境也是爲了測試,但不少公司的預發佈環境是能夠在外網訪問,若是預發佈環境設計的好,就能頗有效的保證系統投產後的安全性,不過最有效能避免上面問題發生的手段仍是本身技術、經驗要過硬。web
Content-type頗有用,多理解下它能夠爲咱們解決更多疑惑,詳細講解這個屬性不是本文的主題,下面我給感興趣的朋友兩個連接,有興趣能夠瞧瞧:ajax
http://baike.baidu.com/view/1547292.htm?fr=aladdinapache
http://tool.oschina.net/commonsjson
大多數時候,在同步請求裏咱們發送http請求給服務端是不須要了解太多content-type的做用,其實不少http頭也不用過多關心,可是有個東西特別是像咱們這種使用漢字而非英語的國家,對字符集的編碼仍是要倍加註意的。跨域
雖然html頭部設置charset的編碼是UTF-8,這是響應頭的字符集,可是若是咱們以這個響應的頁面接着作http請求,那麼這個編碼級會影響到提交請求的字符編碼即咱們若是不作特別指定那麼以後在這個頁面全部請求都是按照該編碼級進行的。那麼問題來了,若是咱們想從一個UTF-8頁面請求一個後臺只能接收GBK編碼數據的接口時候,那麼咱們怎麼更改這個請求的編碼了,問題更進一步點,咱們該如何在同步請求裏控制請求的http頭,至少是很重要的content-type屬性呢?
同步提交核心是form,這和ajax的核心是XMLhttpRequest同樣,在form標籤下有個屬性accept-charset,該屬性規定服務器處理表單數據所接受的字符集。accept-charset 屬性容許您指定一系列字符集,服務器必須支持這些字符集,從而得以正確解釋表單中的數據。
該屬性的值是用引號包含字符集名稱列表。若是可接受字符集與用戶所使用的字符即不相匹配的話,瀏覽器能夠選擇忽略表單或是將該表單區別對待。此屬性的默認值是 "unknown",表示表單的字符集與包含表單的文檔的字符集相同。不過這個屬性在ie下效果並很差,甚至有時會無效,因此若是排除開發者將編碼寫錯的緣由,而是請求方和響應方約定作這樣的轉化,對於字符集的轉化在服務端作是最好的。
Form表單還有一個重要的屬性對http請求有很大的影響那就是enctype,它包含三個取值,以下所示:
值 |
描述 |
application/x-www-form-urlencoded |
在發送前編碼全部字符(默認) |
multipart/form-data |
不對字符編碼。 在使用包含文件上傳控件的表單時,必須使用該值。 |
text/plain |
空格轉換爲 "+" 加號,但不對特殊字符編碼。 |
第一行的值在我本文上篇裏我曾提到過屢次,如今咱們知道它在瀏覽器裏的做用了,form表單enctype默認的屬性值,它讓瀏覽器在請求發送前編碼請求的數據,爲何瀏覽器要編碼請求數據了?根本緣由是由於電腦是美國人發明的,美國人使用英文字母給電腦輸入數據,而像咱們中國人則是使用方塊字漢字,而漢字對於電腦而言是一種特殊字符,不能使用幾個字母組合簡單表示的,放眼全球,這種問題不只僅發生在漢字上,韓文,日本等等非字母的語言都會存在一樣問題,可是網絡傳輸的數據都是二進制,爲了讓請求數據在服務端能被正確還原服務端就必須有一個能容納世界上全部語言的字符集例如UTF-8或則特有的字符集GBK規範進行轉義,這就是application/x-www-form-urlencoded的做用,他告訴瀏覽器你要對請求數據轉碼。Form表單還能傳輸文件,文件是須要特別的解析器來解析,例如圖片要圖片解析器,word文檔要office解析,可是這些文件自己存儲就是二進制,並且是特有的二進制,所以網絡傳輸時候最好能保持原樣,如是乎有了multipart/form-data屬性,有時咱們不想轉碼了那麼就可使用text/plain,這些參數最後都會體如今http請求頭部的content-type屬性裏。
因而可知form表單並不那麼簡單,它也幫咱們開發屏蔽了許多技術細節,因此當咱們不使用form表單提交同步請求時候就有可能掉進這些細節陷阱裏,最多見的一個問題就是使用非form提交的get請求,該get請求會傳參至服務端,而這個參數裏包含中文例如:websit=博客園,若是咱們直接這麼寫,瀏覽器不會給我報錯,可是到了服務端後咱們會發現參數變成亂碼,url形成的亂碼是讓不少初學者頭疼的問題,不過javascript早就預料到了,如是它提供了三對能夠對字符串編碼的函數,分別是: escape,encodeURI,encodeURIComponent,相應3個解碼函數:unescape,decodeURI,decodeURIComponent 。這個問題的根本就是咱們提交請求失去了form的保護,咱們必須手動指定form裏enctype所提供的功能。關於上面三對函數,本文不作過多講述,想了解的朋友能夠問問度娘了。
上面內容是我講同步請求時候講掉的內容,這裏補上了,由上面的內容咱們能夠看出同步提交對http頭部的控制能力是不好的。
XMLHttprequest對http頭有着強大的控制能力,它能夠經過setRequestHeader(名,值)來設置http的請求頭,方法getResponseHeader(名)獲取指定的頭,方法getReponseHeaders()獲取全部響應頭信息,雖然http請求頭對於用戶是不可見的(用戶只關心http請求體的內容,由於請求才是用戶須要的),可是對於開發者而言http請求頭很是重要,由於http請求頭是整個http的領導者,咱們能夠經過控制http請求頭信息達到不少傳統http請求所不能達到的目的。
在使用XMLHttpRequest時候若是咱們不對請求頭作任何修改,默認狀況下瀏覽器會發送如下http頭部信息給服務器,它們分別是:
Accept:瀏覽器可以處理的內容類型; Accpet-Charset:瀏覽器可以顯示字符集; Accept-Encoding:瀏覽器可以處理的壓縮編碼; Accept-Language:瀏覽器當前設置的語言; Connection:瀏覽器和服務器之間鏈接類型; Cookie:當前頁面設置的任何Cookie; Host:發出請求頁面所在的域; Referer:發出請求的頁面的url, User-Agent:瀏覽器的用戶代理字符串。
上面九個頭部信息是全部瀏覽器都會傳送的,不過瀏覽器類型不一樣,頭部信息可能還會有點差別。
雖然setRequestHeader方法能夠修改http頭部信息,可是若是咱們修改上面9個屬性中的某些屬性會發現這種修改有時不頂用,這是某些瀏覽器出於安全考慮不讓開發者輕易更改,好在setRequestHeader方法還能自定義請求頭,若是咱們想作一些與請求頭相關的特別處理,最好是本身定義請求頭。此外setRequestHeader方法使用要在open方法後,send方法前,不然會達不到效果。
接下來是ajax技術裏另一個重點了:回調函數的使用。這裏有一句話:ajax的回調函數是用來處理http請求響應的,也就是說服務端給了瀏覽器請求結果纔會調用ajax的回調函數,這個說法對嗎?我以爲不少人都會認爲這句話是正確的,這裏我先不告訴你們答案,先講講關於ajax回調函數的使用,你們請看下面的代碼:
xmlhttp.onreadystatechange=state_Change; function state_Change() { if (xmlhttp.readyState==4) {// 4 = "loaded" if (xmlhttp.status==200) {// 200 = OK // ...our code here... } else { alert("Problem retrieving XML data"); } } }
XMLHttpRequest的onreadystatechange方法用來接收回調函數,回調函數裏咱們要判斷readyState的取值爲4,status的取值爲200時候,咱們就認爲獲得了成功的響應,不然就是沒有獲得成功的響應。我曾經作過一個嘗試,就是去掉xmlhttp.readyState==4代碼,我發現else裏的alert會執行屢次,點完了這些對話框後請求任然能夠正確處理,這說明了一個問題:onreadystatechange方法是和readystate值有關的,每當readystate值發生改變的時候onreadystatechange就會被執行,其實從onreadystatechange的名字就能知道這點。
XMLHttpRequest裏readystate的取值以下所示:
取值0:未初始化即還沒調用open方法;
取值1:啓動即已經調用了open方法,但尚未調用send方法;
取值2:發送即調用send方法,但尚未收到響應;
取值3:接收即已經接收到部分響應數據;
取值4:完成即接收到了所有響應數據。
因而可知ajax的回調函數並非只有當得到響應時候纔會觸發,一個ajax請求用於記錄回調函數的onreadystatechange方法會被調用5遍,因此咱們必須使用readystate==4來代表響應成功。此外從readystate狀態值咱們也能夠發現onreadystatechange屬性的賦值要放在open方法以前,不然你的寫法是存在隱患的。
瞭解了這個後,咱們看看jQuery的ajax方法的使用,jQuery的ajax方法是對瀏覽器底層ajax操做的封裝,該方法屏蔽了瀏覽器實現之間的差別,同時還提供了一些方法,這些方法是對底層ajax操做的上層封裝,他能讓那些沒有深刻理解原始ajax原理的人以方便,這裏咱們着重看下jQuery裏ajax裏回調函數的使用,下面是我摘抄jQuery文檔裏的內容:
若是要處理$.ajax()獲得的數據,則須要使用回調函數。beforeSend、error、dataFilter、success、complete。 beforeSend 在發送請求以前調用,而且傳入一個XMLHttpRequest做爲參數。 error 在請求出錯時調用。傳入XMLHttpRequest對象,描述錯誤類型的字符串以及一個異常對象(若是有的話) dataFilter 在請求成功以後調用。傳入返回的數據以及"dataType"參數的值。而且必須返回新的數據(多是處理過的)傳遞給success回調函數。 success 當請求以後調用。傳入返回後的數據,以及包含成功代碼的字符串。 complete 當請求完成以後調用這個函數,不管成功或失敗。傳入XMLHttpRequest對象,以及一個包含成功或錯誤代碼的字符串。
上面的success方法就是readystate值爲4,status的值爲200的情形了(實際狀況下status值會更多點,後面再聊這個問題),complete是當請求完成後調用的函數,這個方法其實和status值無關了,只和readystate值爲4有關,beforeSend則是readystate狀態爲1時候使用的,error方法就比較複雜點了,它是一個綜合錯誤的考慮,反正就是不成功就成error了,dataFilter是根據用戶定義的響應數據的類型(dataType)對返回數據作相應的轉化,通常咱們不多使用該函數,都是依賴jQuery內部完成,不過這個回調是在success方法以前執行。
dataFilter回調函數能夠對響應數據進行處理,這裏就引出了一個新問題,咱們經過XMLHttpRequest獲取的響應數據有哪些類型,XMLHttpRequest有兩個屬性用來存儲響應數據,一個是responseText:文本類型的響應數據,其實就是字符串,responseXML:XML文檔類型的響應數據,該屬性只有在http的響應頭是text/xml或者application/xml時候瀏覽器會幫咱們轉化,若是不是上述類型,該屬性則爲null。
咱們使用jQuery的ajax方法時候,經過設定dataType屬性,咱們會得到更加豐富的響應數據,下面是我摘抄的jQuery文檔的內容,以下所示:
dataType:預期服務器返回的數據類型。若是不指定,jQuery 將自動根據 HTTP 包 MIME 信息來智能判斷,好比XML MIME類型就被識別爲XML。在1.4中,JSON就會生成一個JavaScript對象,而script則會執行這個腳本。隨後服務器端返回的數據會根據這個值解析後,傳遞給回調函數。可用值: "xml": 返回 XML 文檔,可用 jQuery 處理。 "html": 返回純文本 HTML 信息;包含的script標籤會在插入dom時執行。 "script": 返回純文本 JavaScript 代碼。不會自動緩存結果。除非設置了"cache"參數。'''注意:'''在遠程請求時(不在同一個域下),全部POST請求都將轉爲GET請求。(由於將使用DOM的script標籤來加載) "json": 返回 JSON 數據 。 "jsonp": JSONP 格式。使用 JSONP 形式調用函數時,如 "myurl?callback=?" jQuery 將自動替換 ? 爲正確的函數名,以執行回調函數。 "text": 返回純文本字符串
上面的大部分操做是jQuery幫咱們完成的,若是咱們定義了dataFilter回調函數,該函數是在類型轉化前執行的,就是說dataFilter操做的數據是ajax返回的原始數據。
當readystate值爲3時候,咱們會作到一些意想不到的功能,readystate爲3的狀態很特別,這個時候服務器已經給出了響應,可是響應數據並無所有發送給瀏覽器,不過此時你操做responseText會發現裏面是有數據的,可是這個數據不全,我曾見過使用XMLHttpRequest這個屬性有人作出了兩個效果,具體以下所述:
效果一:雖然咱們開發時候都是盡全力讓請求和響應時間變得更短,可是某些請求處理是快不起來的例如:上傳文件或者瀏覽器接收超大的數據,若是場景是後者即用戶請求數據量很小,可是接收數據很大,接收時間很長的時候,咱們通常都會作一個等待請求處理的效果,若是這個效果能有精確的進度條,那麼給用戶的體驗是很是好的,http響應報文頭裏有個屬性就是content-length,這個屬性告訴瀏覽器整個響應的大小,通常瀏覽器收到響應,就算這個響應還只有部分數據,這個響應頭也會先發送,之因此提早,是讓服務器知道數據接收到多少就代表接收完畢,因此在readystate爲3時候咱們能夠經過計算收到響應的大小和content-length作比較就能知道響應接收到了那個階段了,這樣進度條的進度就會很精確。
效果二:這個效果是我不少年前無心中看見一個國外網站發現的,惋惜我如今不記得網址了,網頁的做者也是研究ajax的特色的,他經過ajax請求了一篇文章,而後這個文章會在頁面一行行的顯示出來,由於這個效果使用setTimeout函數在瀏覽器也能作,因此這位做者就控制服務端數據返回時間,瀏覽器收到部分數據後就立刻在頁面上顯示出來。
不過上述操做有個特色,就是開發者都會定義一個回調函數,使用setTimeout函數輪詢這個回調函數,setTimeout和回調函數使用是在onreadystatechange回調函數裏面,並且接收數據的字符串要用公共變量存儲,因此這個效果作起來仍是有點難度的,緣由就是readystate狀態爲3時候回調函數只能調用一次。
在jQuery的ajax方法裏有一個屬性ifModified,文檔的解釋是:
(默認: false) 僅在服務器數據改變時獲取新數據。使用 HTTP 包 Last-Modified 頭信息判斷。在jQuery 1.4中,他也會檢查服務器指定的'etag'來肯定數據沒有被修改過。
這個參數說明ajax技術能夠對http的緩存進行操做,因此有些javascript的書裏講解ajax時候表示請求成功會使用兩個響應碼,一個就是常見的200,另外一個是304,304的響應碼代表該請求的響應要從瀏覽器緩存裏取,不須要服務器直接返回了。
爲了說清楚ajax操做緩存,那咱們首先要知道瀏覽器的緩存機制,在web前端優化使用瀏覽器緩存是一個很重要的法則,那麼如何讓瀏覽器能緩存咱們的響應呢?通常有兩種方式:
方式一:經過expires、cache-control來控制,前者是指定緩存到期的具體日期,後者是指定緩存多久後過時例如10年後過時,咱們第一次請求某個資源成功響應碼爲200,這個時候響應頭裏會返回一個last-modified回來,若是該資源咱們設定了緩存,那麼第二次請求在請求頭裏會發送if-Modified-Since屬性,該屬性的值就是第一次返回的last-modified,服務器接收後發現資源沒有改變,服務器就會返回304響應碼回來,304響應碼就是告訴瀏覽器響應結果從瀏覽器緩存裏取。
方式二:使用etag,etag是另外一種緩存失效機制,使用etag的服務器會給指定資源計算一個hash值(通常用md5算的),第一次請求時候服務器會將這個值返回給瀏覽器,第二次請求瀏覽器會將這個值發送給服務器,服務器經過計算資源的hash值和傳來的值做比較後,若是值相等那麼就代表資源沒有更改,這時服務器也會給一個304的響應碼給瀏覽器,讓瀏覽器在本身緩存裏去找對應的響應。
無論是那種方式,若是請求已經被緩存,服務器都會返回304響應碼,使用瀏覽器緩存不表明瀏覽器不發送請求,是否取瀏覽器緩存是服務器來指揮的,只不過304請求不會返回響應體,只有響應頭,並且請求方式是get,這個請求很是小,速度很是快。
一樣當ajax收到了304響應碼時候,ajax就會從瀏覽器緩存裏取響應結果,這個操做XMLHttpRequest會本身幫咱們作掉。
不過關於304的狀況有一點必定要注意,能被瀏覽器緩存的請求只有get請求即原始請求必定要是get請求,post請求時無法被瀏覽器緩存的。
XMLHttpRequest很是強大,我這裏的強大是想說它除了能夠作異步請求還能作同步請求,前面我講了onreadystatechange,講了readystate,其實這兩個東西應該能夠說專屬於異步請求,爲何呢?在之前的文章裏我講到nodejs的做者選擇javascript語言緣由是由於javascript的回調機制,由於有回調就可讓單線程的javascript作出異步的效果,既然是同步提交,定義回調函數還有意義嗎?因此若是咱們使用XMLHttpquest作同步請求,咱們的代碼能夠這麼寫:
xmlhttp.open("GET",url,false); xmlhttp.send(null); if (xmlhttp.status==200) {// 200 = OK // ...our code here... } else { alert("Problem retrieving XML data"); }
代碼會按順序執行的,若是請求很慢,照樣阻塞頁面執行。
看到這裏估計有人會有疑問的,ajax的同步提交有價值嗎?這還不如用form來作同步提交,這個要具體問題具體看了,我在前面講到傳統的同步提交對http的精確控制能力不好,若是某些同步請求咱們須要更多的http控制那麼使用ajax比較好,此外傳統的同步請求響應要麼是覆蓋了原頁面,要麼就是用個新窗體接收響應,可是同步的ajax對響應的處理靈活度更大,相比之下ajax同步請求優點更大些。
在jQuery的ajax裏有一個屬性就是timeout,能夠爲ajax請求設置超時時間,在我前面講解ajax技術時候咱們會發現XMLHttpRequest沒有一個屬性能夠設置這個超時時間屬性,其實在ie8包括ie8以上的版本里,XMLHttpRequest擁有timeout屬性,其餘瀏覽器沒有,可是XMLHttpRequest擁有一個abort方法,這個方法能夠取消異步請求,記住是異步請求,同步操做這個方法無效,無效緣由是同步提交下咱們是沒機會執行該方法的。有了這個方法咱們就能夠模擬ajax請求超時。
哦,寫了8000多字了,看來本篇也沒法寫完我這個主題,因而可知頁面的提交數據方式的學問是很大的。