XMLHttpRequest物件
IE7+,FireFox,Chrome,Opera,Safari建立XHR物件
1 |
var xhr=new XMLHttpRequest(); |
建立XHR物件的相容性寫法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
function createXHR(){ if(typeof XMLHttpRequest!="undefined"){ return new XMLHttpRequest(); }else if(typeof ActiveXObject!="undefined"){ if(typeof arguments.callee.activeXString!="string"){ var versions=["MSXML2.XMLHttp.6.0","MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"], i,len; for(i=0,len=versions.length;i<len;i++){ try{ new ActiveXObject(versions[i]); arguments.callee.activeXString=versions[i]; break; }catch(ex){ } } } return new ActiveXObject(arguments.callee.activeXString); }else{ throw new Error("NO XHR object available"); } } var xhr=new createXHR(); |
XHR用法
傳送同步請求
使用 XHR 時,首先要呼叫 open() 方法,傳遞三個引數:
- 要傳送的請求型別( get , post 等)
- 請求的 url
- 是否非同步傳送
要傳送特定的請求,必需像下面這樣呼叫 send() 方法
1 2 |
xhr.open("get","example.php",false); xhr.send(null); |
這裡 send() 方法接收一個引數,作為請求主體傳送的資料。如果不需要通過請求主體傳送資料,這裡必須傳入 null ,因為這個引數對有些瀏覽器來說是必需的。呼叫 send() 之後,請求就會被分派到伺服器。
由於這次請求是同步的,JavaScript 程式碼會等到伺服器響應之後再繼續執行。在收到響應之後,相應的資料會自動填充XHR物件的屬性,相關的屬性簡介如下:
- responseText: 作為響應主體被返回的文字。
- responseXML: 如果響應的內容型別是 “text/xml”或”application/xml”,這個屬性中將儲存包含著相應資料的XML DOM文件。
- status: 響應的HTTP狀態。
- statusText: HTTP狀態的說明。
接受響應之後,第一步是檢查 status 屬性,以確定響應已經成功返回。狀態碼:
- 200 表示成功
- 304 表示請求的資源並沒有修改,可以直接使用瀏覽器中快取的版本,響應也是有效的
像下面這樣檢查上述這兩種狀態碼的狀態:
1 2 3 4 5 6 7 8 |
xhr.open("get","example.txt",false); xhr.send(null); if((xhr.status >= 200 && xhr.status < 300)|| xhr.status == 304){ alert(xhr.responseText); }else{ alert("Request was unsuccessful: " + xhr.status); } |
注意:無論內容型別是什麼,響應主體的內容都會儲存到 responseText 屬性中;而對於非 XML 資料而言, responseXML 屬性的值將為 null。
傳送非同步請求
向前面這樣傳送同步請求當然沒問題,但多數情況下,我們還是要傳送非同步請求,才能讓 JavaScript 繼續執行而不必等待響應。此時,可以檢測 XHR 物件的 readyState 屬性,該屬性表示請求/響應過程中的當前活動階段。這個屬性可取的值如下:
- 0:未初始化。尚未呼叫 open() 方法。
- 1:啟動。已經呼叫 open() 方法,但尚未呼叫 send() 方法。
- 2:傳送。已經呼叫 send() 方法,但尚未接收響應。
- 3:接收。已經接收到部分響應資料。
- 4:完成。已經接收到全部響應資料,而且已經可以在客戶端使用了。
只要 readyState 屬性的值由一個值變為另一個值,都會觸發一次 readystatechange 事件。可以利用這個事件來檢測每次狀態變化後的 readyState 的值,通常,我們只對 readyState 值為 4 的階段感興趣,因為這時所有的資料都已經就緒。不過,必須在呼叫 open() 之前指定 onreadyState 事件處理程式才能確保跨瀏覽器相容性。例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
var xhr = createXHR(); xhr.onreadyStatechange = function(){ if(xhr.readyState == 4){ if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); }else{ alert("Request was unsuccessful:" + xhr.status ); } } }; xhr.open("get","example.txt",true); xhr.send(null); |
另外,在接收到響應之前還可以呼叫 abort() 方法來取消非同步請求,如下所示:
1 |
xhr.absort(); |
呼叫這個方法後,XHR 物件會停止觸發事件,而且也不再允許訪問任何與響應有關的物件屬性。
HTTP頭部資訊
每個 HTTP 請求和響應都會帶有響應的頭部資訊,有的對開發人員有用,有的也沒有什麼用,XHR 物件也提供了操作這兩種頭部(即請求頭部和響應頭部)資訊的方法。
預設情況下,在傳送 XHR 請求的同事,還會傳送下列頭部資訊。
- Accept: 瀏覽器能夠處理的內容型別。
- Accept-Charset: 瀏覽器能夠顯示的字符集。
- Accept-Encoding: 瀏覽器能夠處理的壓縮編碼。
- Accept-Language: 瀏覽器當前設定的語言。
- Connection: 瀏覽器與伺服器之間連線的型別。
- Cookie: 當前頁面設定的語言。
- Host: 發出請求的頁面所在的域。
- Referer: 發出請求的頁面的URI。
- User-Agent: 瀏覽器的使用者代理字串。
使用 setRequestHeader() 方法可以設定自定義的請求頭部資訊。這個方法接受兩個引數:頭部欄位的名稱和頭部欄位的值。要成功傳送請求頭部資訊,必須在呼叫 open() 方法之後且呼叫 send() 方法之前呼叫 setRequestHeader(),如下面的例子所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var xhr = createXHR(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if((xhr.status >= 200 && xhr.status < 300) || xhr.status = 304){ alert(xhr.responseText); }else{ alert("Request was unsuccessful: " + xhr.status); } } }; xhr.open("get","example.php",true); xhr.setRequestHeader("MyHeader","MyValue"); xhr.send(null); |
伺服器在接收到這種自定義的頭部資訊之後,可移植性響應的後續操作。建議使用自定義的頭部欄位名稱,不要使用瀏覽器正常傳送的欄位名稱。
呼叫 XHR 物件的 getResponseHeader() 方法並傳入頭部欄位名稱,可以取得相應的響應頭部資訊。而呼叫 getAllResponseHeaders() 方法可以取得一個包含所有頭部資訊的長字串。看下面的例子:
1 2 |
var myHeader=xhr.getResponseHeader("MyHeader"); var allHeader=xhr.getAllResponseHeaders(); |
GET請求
GET 是最常見的請求型別,最常用於向伺服器查詢某些資訊。必要時,可以講查詢字串引數追加到 URL 的末尾,以便將資訊傳送給伺服器。對 XHR 而言,位於傳入 open() 方法的 URL 末尾的查詢字串必須經過正確的編碼才行。
使用 GET 請求經常會發生的一個錯誤,及時查詢字串的格式有問題。查詢字串中每個引數的名稱和值必須使用 encodeURIComponent() 進行編碼,然後才能放到 URL 的末尾;而且所有名-值對都必須由和號(&)分隔,例子如下:
1 |
xhr.open("get","example.php?name1=value1&name2=value2",true); |
下面這個函式可以輔助向現有 URL 的末尾新增查詢字串引數:
1 2 3 4 |
function addURLParam(url,name,value){ url += (url.indexOf("?") == -1? "?":"&"); url += encodeURIComponent(name) + "=" + encodeURIComponent(value); } |
使用方法:
1 2 3 4 5 6 7 8 |
var url="example.php"; //新增引數 url = addURLParam(url,"name","Nocholas"); url = addRULParam(rul,"book","Professional JavaScript"); //初始化請求 xhr.open("get",url,false); |
POST請求
POST 請求通常用於向伺服器傳送應該被儲存的資料。POST 請求應該吧資料作為請求的主體提交,而 GET 請求傳統上不是這樣。
預設情況下,伺服器對 POST 請求和提交 Web 表單的請求並不會一視同仁。因此,伺服器端必須有程式來讀取傳送過來的原始資料,並從中解析出有用的部分。不過,我們可以使用 XHR 來模仿表單提交:首先將 Content-Type 頭部資訊設定為 application/x-www-from-urlencoded,也就是表單提交時的內容型別,其次是以適當的格式建立一個字串。
如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function submitData(){ var xhr = createXHR(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if((xhr.status >= 200 && xhr.status < 300) || xhr.status = 304){ alert(xhr.responseText); }else{ alert("Request was unsuccessful: " + xhr.status); } } }; xhr.open("post","example.php",true); xhr.setRequestHeader("Content-Type","application/x-www-from-urlencoded"); var form=document.getElementById("user-info"); xhr.send(serialize(form)); } |
這個函式可以將 ID 為 “user-info” 的表單中的資料序列化之後傳送給伺服器。
XMLHttpRequest2級
XMLHttpRequest 1級只是把已有的 XHR 物件的實現細節描述了出來。而 XMLHttpRequest2級則進一步發展了 XHR。並非所有瀏覽器都完整地實現了 XMLHttpRequest2級規範。
FormData
現代 Web 應用中頻繁使用的一項功能就是表單資料的序列化,XMLHttpRequest2級為此定義了 FormData 型別。FormData 為序列化表單以及建立與表單格式相同的資料提供了便利。下面程式碼建立了 FormData 物件,並向其中新增了一些資料。
1 2 |
var data = new FormData(); data.append("name","Nicholas"); |
這個 append() 方法接受兩個引數:鍵和值,分別對應表單欄位的名字和欄位中包含的值。可以像這樣新增任意多的鍵值對。而通過向 FormData 建構函式中傳入表單元素,也可以用表單元素的資料預先向其中填入鍵值對:
1 |
var data=new FormData(document.forms[0]); |
建立了 FormData 的例項後,可以將它直接傳給 XHR 的 send() 方法,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function submitData(){ var xhr = createXHR(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if((xhr.status >= 200 && xhr.status < 300) || xhr.status = 304){ alert(xhr.responseText); }else{ alert("Request was unsuccessful: " + xhr.status); } } }; xhr.open("post","example.php",true); var form=document.getElementById("user-info"); xhr.send(new FormData(form)); } |
使用 FormData 的方便之處在於不必明確地在 XHR 物件上設定請求頭部。XHR 物件能夠識別傳入的資料型別是 FormData 的例項,並配置適當的頭部資訊。
支援 FormData 的瀏覽器有 Firefox4+,Safari5+,Chrome和Android3+版WebKit。
跨域資源共享
通過 XHR 實現 Ajax 通訊的一個主要限制,來源於跨域安全策略。
CORS(Cross-Origin Resource Sharing,跨域資源共享)背後的基本思想,及時使用自定義的HTTP頭部讓瀏覽器與伺服器進行溝通,從而決定請求或響應是應該成功,還是應該失敗。
比如一個簡單地使用 GET 或 POST 傳送的請求,它沒有自定義的頭部,而主題內容是 text/plain。在傳送該請求時,需要給它附加一個額外的 Origin 頭部,其中包括請求頁面的源資訊(協議、域名、埠),以便伺服器根據這個頭部資訊來決定是否給予響應,下面是 Oringin 頭部的一個示例:
1 |
Origin: http://www.nczonline.net |
如果伺服器認為這個請求可以接受,就在 Access-Control-Allow-Origin 頭部中回發相同的源資訊(如果是公共資源,可以回發 “*”)。例如:
1 |
Access-Control-Allow-Origin: http://www.nczonline.net |
如果沒有這個頭部,或者有這個頭部但源資訊不匹配,瀏覽器就會駁回請求。正常情況下,瀏覽器會處理請求。注意,請求和響應都不包含 cookie 資訊。
IE 對 CORS 的實現
微軟在 IE8 中引入了 XDR (XDomainRequest) 型別。這個物件與 XHR 類似,但能實現安全可靠的跨域通訊。XDR 物件的安全機制部分實現了 W3C 的 CORS 規範。以下是 XDR 與 XHR 的一些不同之處。
- cookie 不會隨請求傳送,也不會隨響應返回。
- 只能設定請求頭部資訊的 Content-Type 欄位。
- 不能訪問響應頭部資訊。
- 只支援GET和POST請求。
所有的 XDR 請求都是非同步執行的,不能用它來建立同步請求。請求返回之後,會觸發 load 事件,響應的資料也會儲存在 responseText 屬性中。
在接收到響應後,你只能訪問響應的原始文字;沒有辦法確定響應的狀態程式碼。而且,只要響應有效就會觸發 load 事件,如果失敗(包括響應中缺少 Access-Control-Allow-Origin頭部),就會觸發 error 事件。遺憾的是,除了錯誤本身之外,沒有其他資訊可用,因此唯一能夠確定的就只有請求未成功了。要檢測錯誤,可以像下面這樣指定一個 onerror 事件處理程式。
1 2 3 4 5 6 7 8 9 |
var xdr=new XDomainRequest(); xdr.onload=function(){ alert(xdr.responseText); }; xdr.onerror=function(){ alert("An erro occurred."); }; xdr.open("get","http://www.somewhere-else.com/page"); xdr.send(null); |
為支援 POST 請求,XDR 物件提供了 contentType 屬性,用來表示傳送資料的格式,如下所示:
1 2 3 4 5 6 7 8 9 10 |
var xdr=new XDomainRequest(); xdr.onload=function(){ alert(xdr.responseText); }; xdr.onerror=function(){ alert("An erro occurred."); }; xdr.open("post","http://www.somewhere-else.com/page"); xdr.contentType="application/x-www-form-urlencoded"; xdr.send("name1=value1&name2=value2"); |
這個屬性通過 XDR 物件影響頭部資訊的唯一方式。
其他瀏覽器對 CORS 的實現
Forefox3.5+,Sarari4+,Chrome,iOS 版 Sarari 和 Android 平臺中的 WebKit 都通過 XMLHttpRequest 物件實現了對 CORS 的原生支援。在嘗試開啟不同來源的資源時,無需額外編寫程式碼就可以出發這個行為。要請求位於另一個域中的資源,使用標準的 XHR 物件並在 open() 方法中傳入絕對 URL 即可,例如:
1 2 3 4 5 6 7 8 9 10 11 12 |
var xhr=createXHR(); xhr.onreadystatechange=function(){ if(xhr.readyState == 4){ if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); }else{ alert("Request was unsuccessful: " + xhr.status); } } }; xhr.open("get","http://www.somewhere-else.com/page/",true); xhr.send(null); |
與 IE 中的 XDR 物件不同,通過跨域 XHR 物件可以訪問 status 和 statusText 屬性,而且還支援同步請求,跨域 XHR 物件也有一些限制,但為了安全,這些限制是必需的,一下就是這些限制。
- 不能使用 setRequestHeader() 設定自定義頭部。
- 不能傳送和接收 cookie。
- 呼叫 getAllResponseHeaders() 方法總會返回空字串。
對於本地資源,最好使用相對 RUL,在訪問遠端資源時再使用絕對 URL。
Preflighted Requests
CORS 通過一種叫做 Preflighted Requests 的透明伺服器驗證機制支援開發人員使用自定義的頭部,GET 或 POST 之外的方法,以及不同型別的主體內容,在使用下列高階選項來傳送請求時,就會像伺服器傳送一個 Preflight 請求。這種請求使用 OPTIONS 方法,傳送下列頭部。
- Origin: 與簡單的請求相同。
- Access-Control-Request-Method: 請求自身使用的方法。
- Access-Control-Request-Headers: (可選) 自定義的頭部資訊,多個頭部以逗號分隔。
以下是一個帶有自定義頭部的 NCZ 的使用 POST 方法傳送的請求。
1 2 3 |
Origin: http://www.nczonline.net Access-Control-Request-Method: POST Access-Control-Request-Headers: NCZ |
傳送這個請求後,伺服器可以決定是否允許這種型別的請求。伺服器通過在響應中傳送如下頭部與瀏覽器進行溝通。
1 2 3 4 |
Access-Control-Allow-Origin: 與簡單的請求相同。 Access-Control-Allow-Method: 允許的方法,多個方法以逗號分隔。 Access-Control-Allow-Headers: 允許的頭部,多個頭部以逗號分隔。 Access-Control-Max-Age: 應該將這個 Preflight 請求快取多長時間(以秒錶示)。 |
例如:
1 2 3 4 |
Access-Control-Allow-Origin: http://www.nczonline.net Access-Control-Allow-Method: GET,POST Access-Control-Allow-Headers: NCZ Access-Control-Max-Age: 1728000 |
Preflight 請求結束後,結果將按照響應中指定的事件快取起來。而為此付出的代價只是第一次傳送這種請求時會多一次 HTTP 請求。
支援 Preflight 請求的瀏覽器包括 Firefox3.5+, Sarari4+ 和 Chrome。IE10 及更早版本都不支援。
帶憑據的請求(Requests with Credential)
預設情況下,跨域請求不提供憑據(Cookie、HTTP 認證及客戶端 SSL 證明等)。通過將 withCredentials 屬性設定為 true,可以指定某個請求應該傳送憑據。如果伺服器接收帶憑據的請求,會用下面的 HTTP 頭部來響應。
1 |
Access-Control-Allow-Credentials: true |
如果傳送的是帶憑據的請求,單伺服器的響應中沒有包含這個頭部,那麼瀏覽器就不會把響應交給 JavaScript (於是,responseText 中將是空字串,status 的值為0,而且會呼叫 onerror() 事件處理程式)。
支援 withCredentials 屬性的瀏覽器有 Firefox3.5+,Sarari4+ 和 Chrome。IE10 及更早版本都不支援。
下面這篇文章講的特別好,介紹了簡單地跨域請求、Preflight 請求和帶憑據的請求三種請求的區別和請求流程。
文章地址:http://www.cnblogs.com/loveis715/p/4592246.html
跨瀏覽器的 CORS (相容性 CORS的寫法)
即使瀏覽器對 CORS 的支援程度並不都一樣,但所有瀏覽器都支援簡單地(非 Preflight 和不帶憑據的)請求,因此有必要實現一個跨瀏覽器的方案。檢測 XHR 是否支援 CORS 的最簡單的方式,就是檢查是否存在 withCredentials 屬性,再結合檢測 XDomainRequest 物件是否存在,就可以兼顧所有瀏覽器了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function createCORSRequest(method,url){ var xhr=new XMLHttpRequest(); if("withCredentials" in xhr){ xhr.open(method,url,true); }else if(typeof XDomainRequest != "undefined"){ xhr = new XDomainRequest(); xhr.open(method,url); }else { xhr = null; } return xhr; } var request = createCORSRequest("get","http://www.somewhere-else.com/page/"); if(request){ request.onload = function(){ //對request。responseText進行處理 }; request.send(); } |
其他跨域技術
JSONP
JSONP 原理:JSONP 是通過動態script
元素來使用的,使用時可以作為 src 屬性指定一個跨域 URL。 這裡的script
元素有能力不受限制地從其他域載入資源。因為 JSONP 是有效的 JavaScript 程式碼,所以在請求完成後,即在 JSONP 響應載入到頁面中以後,就會立即執行。來看一個例子。
1 2 3 4 5 6 7 |
function handleResponse(response){ alert("you are at IP address "+ response.ip+", which is in "+response.city+", "+response.region_name); } var script=document.createElement("script"); script.src="http://freegeoip.net/json/?callback=handleResponse"; document.body.insertBefore(script,document.body.firstChild); |
下面這篇文章介紹了 JSON 和 JSONP,值得一看
文章地址:http://kb.cnblogs.com/page/139725/
其他跨域
其他跨域技術還有 Comet、SSE、WebSockets等。感興趣的讀者可以查閱相關資料進行了解。