歡迎光臨
每天分享高質量文章

逐漸深入地理解Ajax

Ajax的基本原理是:XMLHttpRequest物件(簡稱XHR物件),XHR為向伺服器傳送請求和解析伺服器響應提供了流暢的介面。能夠以非同步方式從伺服器獲得更多資訊。意味著使用者不必掃清頁面也能取得新資料,然後透過DOM將資料插入到頁面中。

XMLHttpRequest物件方法如下:

about():停止當前的請求;

open(“method”,”URL”,[asyncFlag]) :

等常見的方法;

XHR的基本用法:

在使用XHR物件時,要呼叫的第一個方法是open()方法,它有三個引數,第一個引數是:需要傳送請求的型別(get或者post),第二個引數是請求的url,第三個引數是請求的布林值(true是非同步,false是同步);

xhr.open(‘get’,’http://127.0.0.1/ajax/ajax.php’,false);

如上程式碼會啟動一個get請求ajax.php,但是請註意:open方法並不會真正發請求,而只是啟動一個請求以備傳送;

要傳送真正請求必須使用send()方法;如下:

xhr.open(‘get’,’http://127.0.0.1/ajax/ajax.php’,false);

xhr.send(null);

send的方法接收一個引數,需要請求傳送的資料,如果請求不需要傳送資料,需要傳送一個null,因為對於有些瀏覽器這是必須的;上面第三個引數傳的是false,是同步請求,伺服器接收到響應後再繼續執行後面的程式碼,響應後的資料會自動填充XHR物件的屬性,XHR有以下屬性:

responseText: 作為響應主體被傳回的文字。

responseXML: 如果響應的內容是”text/xml” 或 “application/xml”,這個屬性將儲存包含響應資料的XML DOM檔案;

status: 響應http狀態;

statusText: http狀態說明;

在接收到響應後,第一步是檢查status狀態,如果狀態時200,說明已經成功傳回,此時responseText屬性已經就緒;如果狀態是304,說明資源未被修改,可以直接使用瀏覽器快取的版本,當然,響應是有效的;如下ajax請求程式碼;

// 建立xhr物件方法如下:

function createXHR(){

var xhr;

if (window.XMLHttpRequest){

// code for IE7+, Firefox, Chrome, Opera, Safari

xhr=new XMLHttpRequest();

}else{ // code for IE6, IE5

xhr=new ActiveXObject(“Microsoft.XMLHTTP”);

}

return xhr;

}

var xhr = createXHR();

xhr.open(‘get’,’http://127.0.0.1/ajax/ajax.php’,false);

xhr.send(null);

if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {

console.log(xhr.responseText);

}else {

console.log(xhr.status);

}

ajax.php程式碼如下:

$data = json_decode(file_get_contents(“php://input”));

echo (‘{“id” : ‘ . $data->id . ‘, “age” : 24, “sex” : “boy”, “name” : “huangxueming”}’);

?>

發ajax請求後,在控制臺中列印如下:

說明已經請求成功;

上面的demo程式碼是同步請求,但是有時候我們需要傳送非同步請求,才能讓javascript後續程式碼繼續執行而不會堵塞,此時,我們可以檢測XHR物件的readyState屬性,該屬性表示請求/響應當前活動階段;這個屬性取值如下:

0:未初始化。尚未呼叫open()方法;

1:啟動。已經呼叫open()方法,但未呼叫send()方法;

2:傳送。已經呼叫send()方法,但尚未接收到響應;

3:接收。已經接收到部分資料;

4:完成。已經接收到全部響應資料,而且可以在客戶端使用了;

readyState屬性值由一個值變為另一個值,就會觸發一次readystatechange事件。可以利用這個事件檢測每次狀態變化後的readyState值,但是必須在呼叫open()方法之前指定onreadystatechange事件;如下程式碼:

// 建立xhr物件方法如下:

function createXHR(){

var xhr;

if (window.XMLHttpRequest){

// code for IE7+, Firefox, Chrome, Opera, Safari

xhr=new XMLHttpRequest();

}else{

// code for IE6, IE5

xhr=new ActiveXObject(“Microsoft.XMLHTTP”);

}

return xhr;

}

var xhr = createXHR();

xhr.onreadystatechange = function(){

if(xhr.readyState == 4) {

if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {

console.log(xhr.responseText);

}else {

console.log(xhr.status);

}

}

}

xhr.open(‘get’,’http://127.0.0.1/ajax/ajax.php’,false);

xhr.send(null);

如上程式碼,在處理onreadystatechange事件時使用了xhr物件,沒有使用this物件,原因是onreadystatechange事件處理程式的作用域的問題;如果使用this物件,在有的瀏覽器下會執行失敗,或者導致錯誤發生;

理解Http頭部資訊

每個http請求和響應都會帶有響應的頭部資訊,xhr物件也提供了操作這兩種頭部(請求頭部和響應頭部的)資訊的方法;

預設情況下,在傳送XHR請求的同時,還會傳送下列頭部資訊:

Accept: 瀏覽器能夠處理的內容型別;

Accept-Charset: 瀏覽器能夠顯示的字符集;

Accept-Encoding: 瀏覽器能夠處理的壓縮編碼;

Accept-Language: 瀏覽器當前設定的語言;

Connection: 瀏覽器與伺服器之間連線的型別;

Cookie:當前頁面設定的cookie;

Host:發出請求頁面所在的域;

Referer:發出請求的頁面url。

User-Agent: 瀏覽器的使用者代理字串。

如下ajax.php請求所示:

我們還可以使用setRequestHeader()方法可以設定自定義的請求頭部資訊,這個方法接收2個引數:頭部欄位的名稱和頭部欄位的值;要成功傳送請求頭部資訊,必須在呼叫open()方法之後且呼叫send()方法之前呼叫setRequestHeader();如下demo所示:

xhr.open(‘get’,’http://127.0.0.1/ajax/ajax.php’,false);

xhr.setRequestHeader(“myHeader”,”myValue”);

xhr.send(null);

截圖如下:

呼叫getAllResponseHeaders()方法則可以取得一個包含所有頭部資訊的長字串,如上程式碼中在onreadystatechange事件中新增getAllResponseHeaders()方法:

xhr.onreadystatechange = function(){

if(xhr.readyState == 4) {

if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {

var myHeader = xhr.getAllResponseHeaders();;

console.log(myHeader);

}else {

console.log(xhr.status);

}

}

列印如下:

理解GET請求

在ajax中,有常見的get請求或者post請求,使用get請求時,我們是把引數的名-值對經過encodeURIComponent()進行編碼,然後放到URL的末尾,所有名值對必須由和好(&)分割;如下程式碼:

xhr.open(‘get’,’http://127.0.0.1/ajax/ajax.php?name1=value1&name2;=value2′,true);

下麵我們可以封裝一個方法可以輔助向現有的URL的末尾新增查詢字串引數;

function addURLParam(url,name,value) {

url += url.indexOf(“?”) == -1 ? “?” : “&”;

url += encodeURIComponent(name) + “=” + encodeURIComponent(value);

return url;

}

如下程式碼測試:

// 建立xhr物件方法如下:

function createXHR(){

var xhr;

if (window.XMLHttpRequest){// code for IE7+, Firefox, Chrome, Opera, Safari

xhr=new XMLHttpRequest();

}else{ // code for IE6, IE5

xhr=new ActiveXObject(“Microsoft.XMLHTTP”);

}

return xhr;

}

var xhr = createXHR();

xhr.onreadystatechange = function(){

if(xhr.readyState == 4) {

if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {

var myHeader = xhr.getAllResponseHeaders();;

console.log(myHeader);

console.log(xhr.responseText);

}else {

console.log(xhr.status);

}

}

}

var url = “http://127.0.0.1/ajax/ajax.php”;

url = addURLParam(url,”name1″,”value1″);

url = addURLParam(url,”name2″,”value2″);

xhr.open(‘get’,url,true);

xhr.setRequestHeader(“myHeader”,”myValue”);

xhr.send(null);

function addURLParam(url,name,value) {

url += url.indexOf(“?”) == -1 ? “?” : “&”;

url += encodeURIComponent(name) + “=” + encodeURIComponent(value);

return url;

}

如下截圖所示:

理解POST請求:

Post請求時作為請求的主體提交,post請求的主體可以是非常多的資料,而且格式不限;在open方法的第一個引數傳入post,就可以初始化post請求,如下:xhr.open(‘post’,”http://127.0.0.1/ajax/ajax.php”,true);

接下來我們需要使用send()方法來傳送資料,傳入的引數可以是任何的字串或者form表單序列化之後的資料;比如我們現在可以使用xhr來模仿表單提交資料,首先我們需要將Content-Type頭部資訊設定為application/x-www-form-urlencoded, 也就是表單提交的內容型別,其次以適當的格式建立一個字串,比如form表單序列化資料,然後透過xhr傳送到伺服器端,

如下HTML程式碼:

JS程式碼如下:

// 序列化的程式碼

function serialize(form) {

var arrs = [],

field = null,

i,

len,

j,

optLen,

option,

optValue;

for(i = 0,len = form.elements.length; i < len; i++) {

field = form.elements[i];

switch(field.type) {

case “select-one”:

case “select-multiple”:

if(field.name.length) {

for(j = 0,optLen = field.options.length; j < optLen; j++) {

option = field.options[j];

if(option.selected) {

optValue = ”;

if(option.hasAttribute) {

optValue = option.hasAttribute(“value”) ? option.value : option.text;

}else {

optValue = option.attributes[“value”].specified ? option.value : option.text;

}

arrs.push(encodeURIComponent(field.name) + “=” +encodeURIComponent(optValue));

}

}

}

break;

case undefined: //欄位集

case “file”: // 檔案輸入

case “submit”: // 提交按鈕

case “reset”: // 重置按鈕

case “button”: // 自定義按鈕

break;

case “radio”: // 單選框

case “checkbox”: // 核取方塊

if(!field.checked) {

break;

}

/* 執行預設動作 */

default:

// 不包含沒有名字的表單欄位

if(field.name.length) {

arrs.push(encodeURIComponent(field.name) + “=” +encodeURIComponent(field.value));

}

}

}

return arrs.join(“&”);

}

var url = “http://127.0.0.1/ajax/ajax.php”;

xhr.open(‘post’,”http://127.0.0.1/ajax/ajax.php”,true);

xhr.setRequestHeader(“Content-Type”,”application/x-www-form-urlencoded”);

var form = document.getElementById(“form”);

xhr.send(serialize(form));

之後我們可以看到form表單序列化之後傳過去的資料如下:

XMLHttpRequest 2級

XMLHttpRequest 1級只是把已有的xhr物件的實現細節描述出來了,而XMLHttpRequest 2級是在原來的基礎上增加了一些規範,但所有的瀏覽器只是實現了他規定的部分內容;如下幾個:

理解FormData

現在web應用中頻繁使用的一項功能是form表單序列化,XMLHttpRequest 2級定義了FormData型別,使用FormData獲取資料與表單序列化資料一樣,但是更簡潔方便;

支援FormData瀏覽器有:firefox4+, safari5+, chrome和Android3+版的webkit.

比如我現在提交form表單資料如下demo:

HTML程式碼:

JS程式碼如下:

var url = “http://127.0.0.1/ajax/ajax.php”;

xhr.open(‘post’,”http://127.0.0.1/ajax/ajax.php”,true);

var form = document.getElementById(“form”);

xhr.send(new FormData(form));

在chrome瀏覽器下如下:

在firefox瀏覽器如下:

使用formData的方便之處體現在不必明確地在XHR物件上設定請求頭部,XHR物件能識別傳入的資料型別是FormData實體,並配置適當的頭部資訊。

理解超時設定

IE8為XHR物件添加了一個timeout屬性,表示請求在等待響應多少毫秒之後停止,在給timeout設定一個數值後,如果在規定的時間之內瀏覽器沒有接受到響應,那麼就會觸發ontimeout事件,那麼就會終止ajax請求,如下程式碼:

var url = “http://127.0.0.1/ajax/ajax.php”;

xhr.open(‘get’,”http://127.0.0.1/ajax/ajax.php”,true);

xhr.timeout = 1;

xhr.ontimeout = function(){

alert(“111”);

};

xhr.send(null);

如上我把程式碼timeout設定1毫秒,如果請求1毫秒之後沒有傳回資料的話,就執行ontimeout事件,並且請求停止掉,目前我測試的瀏覽器chrome和firefox都支援;如下chrome瀏覽器截圖如下:

進度事件

Progress Events規範是W3C的一個工作草案,定義了與客服端伺服器通訊有關的事件,這些事件最早是針對XHR操作的,有以下6個事件;

loadstart: 在接收到響應資料的第一個位元組時觸發;

progress: 在接收響應期間持續不斷的觸發;

error: 在請求發生錯誤時觸發;

abort: 在因為呼叫abort()方法而終止連線時觸發;

load:在接收到完整的響應資料時觸發;

loadend:在通訊完成或者觸發error,abort或load事件後觸發;(這個事件基本上不使用的。)

支援前5個事件的瀏覽器有:firefox3.5+,safari4+,chrome,IOS版的safari和android版的webkit,opera(從11開始),IE8+只支援load事件;

load事件

Firefox在實現XHR物件的某個版本時,為了簡化非同步互動模型,實現了load事件,用以替代readystatechange事件,響應接收完畢後觸發load事件,因此就沒有必要檢查readyState屬性了;

目前支援的瀏覽器有:firefox,opera,chrome和safari

如下程式碼演示:

var xhr = createXHR();

xhr.onload = function(){

if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {

console.log(xhr.responseText);

}else {

console.log(xhr.status);

}

}

xhr.open(‘get’,”http://127.0.0.1/ajax/ajax.php”,true);

xhr.send(null);

progress事件

這個事件在瀏覽器接收資料期間週期性的觸發,而onprogress事件處理程式會接收一個event物件,包含三個屬性,lengthComputable是一個表示進度資訊是否可用的布林值;position表示已經接收的位元組數,totalSize表示根據Content-Length響應頭部確定的預期總位元組數。如下程式碼:

var xhr = createXHR();

xhr.onload = function(){

if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {

console.log(xhr.responseText);

}else {

console.log(xhr.status);

}

}

xhr.onprogress = function(event) {

var divStatus = document.getElementById(“status”);

if (event.lengthComputable){

divStatus.innerHTML = “Received ” + event.position + ” of ” +

event.totalSize +” bytes”;

}

};

xhr.open(‘get’,”http://127.0.0.1/ajax/ajax.php”,true);

xhr.send(null);

截圖執行如下:

跨源資源共享

透過XHR實現ajax通訊的一個主要限制,來源於跨域安全策略。預設情況下,XHR物件只能訪問與包含它的頁面位於同一個域中的資源,因為瀏覽器這樣做的限制就是防止一些惡意操作;那麼CORS(跨源資源共享)的基本原理是:需要由伺服器傳送一個響應標頭就可以讓瀏覽器與伺服器進行溝通;

比如我現在做一個demo來試著看,假如我現在在hosts檔案下系結2個IP地址:如下:

127.0.0.1 abc.example1.com

127.0.0.1 def.example2.com

那麼現在我 abc.example1.com下有一個頁面ajax.html,如下程式碼:

var xhr = createXHR();

xhr.onload = function(){

if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {

console.log(xhr.responseText);

}else {

console.log(xhr.status);

}

}

xhr.open(‘POST’,”http://def.example2.com/ajax/ajax.php”,true);

xhr.send(null);

如上程式碼,在給域下def.example2.com 下的ajax.php發ajax請求,因為是同源策略問題,肯定不成功,如下所示:

現在我們在def.example2.com域下php設定頭部即可;如下程式碼:

essay-header(“Access-Control-Allow-Origin: *”);

ajax.php程式碼如下:

essay-header(“Access-Control-Allow-Origin: *”);

$data = json_decode(file_get_contents(“php://input”));

echo (‘{“id” : ‘ . $data->id . ‘, “age” : 24, “sex” : “boy”, “name” : “huangxueming”}’);

?>

這樣就可以認為大功告成,很高興,看到書上或者網上查詢資料後,也是這麼說的,但是呢 當我再次請求這個頁面的時候http://abc.example1.com/ajax/ajax.html 還是發現因為同源策略的問題,請求不成功,檢視控制檯這樣的錯誤,如下:

XMLHttpRequest cannot load http://def.example2.com/ajax/ajax.php. No ‘Access-Control-Allow-Origin’ essay-header is present on the requested resource. Origin ‘http://abc.example1.com’ is therefore not allowed access.

也是同源策略的問題,不允許訪問;繼續透過網上查詢資料,發現原來是我的php.ini裡面的配置出了問題,在php安裝目錄下找到php.ini檔案output_buffering預設為off的。我現在把它設為on就OK了。如下截圖所示:

我們繼續重啟下php伺服器,即可看到請求成功,這時候我們會喜出往外;這時候我們再看看php請求如下設定:

在php檔案頭部設定essay-header,essay-header(“Access-Control-Allow-Origin: *”);星號意思是所有的請求都可以,但是設定並不安全,所以我們可以針對當前的這個頁面域下設定,如下:

essay-header(“Access-Control-Allow-Origin: http://abc.example1.com”);

$data = json_decode(file_get_contents(“php://input”));

echo (‘{“id” : ‘ . $data->id . ‘, “age” : 24, “sex” : “boy”, “name” : “huangxueming”}’);

?>

我們繼續截圖如下看看:

同樣也可以訪問;但是上面的在IE7-8下還是會報錯,因為同源策略的問題,會報錯,如下截圖所示:

標準瀏覽器下比如chrome或者firefox不會報錯,那麼我們可以看下在IE下,IE如何實現對CORS的支援;

IE對CORS的實現方式如下:

微軟在IE中引入XDR(XDomainRequest)型別,這個物件與XHR類似,但是能實現安全可靠的跨域通訊,XDR物件部分實現了W3C的CORS規範;XDR與XHR不同之處如下:

  1. Cookie不會隨請求傳送,也不會隨響應傳回。
  2. 只能設定請求頭部資訊中的Content-Type欄位。
  3. 不能訪問響應頭部資訊。
  4. 只支援get和post請求。

這些變化使CSRF(Cross-Site Request Forgery,跨站點請求偽造)和XSS(Cross-Site Scripting,跨站點指令碼)的問題得到了緩解,被請求的資源可以根據它認為合適的任意資料(使用者代理,來源頁面等)來決定是否設定Accept-Control-Allow-Origin頭部。作為請求的一部分,Origin頭部的值表示請求的來源域,以便遠端資源明確地識別XDR請求。

XDR物件的使用方法與XHR物件非常相似,先建立一個XDomainRequest物件,如下:

var xdr = new XDomainRequest();

再呼叫open()方法,再呼叫send()方法;但是XDR物件的open()方法只接受2個引數,請求的型別和URL;

所有XDR的請求都是非同步的,不能用來建立同步請求,請求成功後,會觸發load事件,響應的資料儲存在responseText屬性中;如下demo,我們繼續看下在IE7-8下如何可以跨域請求成功;如下程式碼:

var xdr = new XDomainRequest();

xdr.onload = function() {

alert(xdr.responseText);

};

xdr.open(‘POST’,”http://def.example2.com/ajax/ajax.php”,true);

xdr.send(null);

現在我們掃清下頁面可以看到請求成功了,如下所示:

如果響應失敗的話,會呼叫error方法,通知開發人員,如下程式碼:

var xdr = new XDomainRequest();

xdr.onload = function() {

alert(xdr.responseText);

};

xdr.onerror = function(){

alert(“響應失敗”);

};

xdr.open(‘POST’,”http://def.example2.com/ajax/ajax.php”,true);

xdr.send(null);

與XHR一樣,XDR物件也支援timeout屬性以及ontimeout事件處理程式,如下程式碼:

xdr.timeout = 1;

xdr.ontimeout = function(){

alert(“Request took too long.”);

};

請求超過1毫秒後沒有響應,就呼叫ontimeout事件;

標準瀏覽器下實現對CORS的實現

其實實現方式在第一次講解跨域的時候我們已經講過了,可以翻到上面,但是我們現在重新來理一遍;

Firefox3.5+,Safari4+,chrome,ios版的safari和android平臺中的webkit都透過XMLHttpRequest物件實現了CORS的原生支援;如下程式碼:

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function(){

if (xhr.readyState == 4){

if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {

alert(xhr.responseText);

}else {

console.log(xhr.status);

}

}

}

xhr.open(‘POST’,”http://def.example2.com/ajax/ajax.php”,true);

xhr.send(null);

如上程式碼即可就可以實現在標準瀏覽器下ajax的跨域通訊問題;

與IE中的XDR物件不同,透過跨域XHR物件可以訪問status和statusText屬性,而且還支援同步請求,跨域XHR物件也有一些限制,如下限制:

  1. 不能使用setRequestHeader()設定自定義頭部;
  2. 不能傳送和接收cookie。
  3. 呼叫getAllResponseHeaders()方法總會傳回空字串。

跨瀏覽器的CORS的實現

如上介紹的是對IE下和標準瀏覽器下2種方案實現跨域的實現方案,下麵我們可以來封裝下對常用的所有瀏覽器支援下,首先我們先檢查是否存在withCredentials屬性(標準瀏覽器下有這個屬性),再結合檢測XDomainRequest物件是否存在(就可以檢測到IE),如下是所有封裝的程式碼:

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(‘POST’,”http://def.example2.com/ajax/ajax.php”);

if(request) {

request.onload = function(){

alert(request.responseText);

}

request.send();

}

瀏覽器支援程度:IE7+,firefox3.5+,safari4+,chrome3+等;

JSONP跨域技術的基本原理

Jsonp跨域get請求是如何實現的;我們先來瞭解下為什麼會出現跨域?

Javascript是一種在web開發中經常使用的前端動態指令碼技術,在javascript中,有一個很重要的安全限制,被稱為”same-Origin-Policy”同源策略,這一策略對於javascript程式碼能夠訪問的頁面內容作了很重要的限制,即javascript只能訪問與包含它的檔案在同一域下的內容;

JSONP的基本原理是:利用在頁面中建立

贊(0)

分享創造快樂