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代碼如下:
<?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代碼:
<form id="form"> <input name="user-name" value="aaa"/> <input name="user-email" value="233"/> </form>
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代碼:
<form id="form"> <input name="user-name" value="aaa"/> <input name="user-email" value="233"/> </form>
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設置頭部即可;如下代碼:
header("Access-Control-Allow-Origin: *");
ajax.php代碼如下:
<?php 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' 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文件頭部設置header,header("Access-Control-Allow-Origin: *");星號意思是所有的請求都可以,但是設置並不安全,所以我們可以針對當前的這個頁面域下設置,如下:
<?php 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不同之處如下:
- Cookie不會隨請求發送,也不會隨響應返回。
- 只能設置請求頭部信息中的Content-Type字段。
- 不能訪問響應頭部信息。
- 只支持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對象也有一些限制,如下限制:
- 不能使用setRequestHeader()設置自定義頭部;
- 不能發送和接收cookie。
- 調用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的基本原理是:利用在頁面中創建<script>節點的方法向不同域提交http請求的方法稱為JSONP。
比如我現在的ajax.html頁面http://abc.example1.com/ajax/ajax.html 下提交ajax請求http://def.example2.com/ajax/ajax.php get請求,我們可以在ajax.html頁面動態的創建script標簽,如下:
var eleScript= document.createElement("script"); eleScript.type = "text/javascript"; eleScript.src = "http://def.example2.com/ajax/ajax.php"; document.getElementsByTagName("HEAD")[0].appendChild(eleScript);
當get請求從http://def.example2.com/ajax/ajax.php 返回時,可以返回一斷javascript代碼,這段代碼會自動執行,可以用來負責調用頁面http://abc.example1.com/ajax/ajax.html 中的一個callback函數;
理解JSONP執行過程如下:
首先在客戶端注冊一個callback(比如jsonpcallback),然后把callback名字(比如叫jsonp123456)傳給服務器端,服務器端得到callback名字后,需要用jsonp123456()把將要輸出的json內容包括起來,此時,服務器生成的json數據才能被客戶端正確接收;然后以javascript語法的方式,生成一個function,function的名字就是傳遞回來的參數jsonp123456.然后就可以在客戶端直接運行調用jsonp123456這個函數了;
JSONP的優點:它不像XMLHttpRequest對象實現ajax請求受到同源策略的限制,它在所有的瀏覽器都支持,比如古老的IE6也支持,並且在請求完成后可以通過callback的方式傳回結果;
JSONP的缺點:只支持get請求,不支持post請求,它只支持http跨域的請求情況,不能解決不同域的兩個頁面之間如何進行javascript調用的問題;

