基本概念
Ajax 全稱是異步的 JavaScript 和 XML 蒂窒。 通過在后臺與服務器進行少量數(shù)據(jù)交換躁倒,AJAX 可以使網頁實現(xiàn)異步更新。這意味著可以在不重新加載整個網頁的情況下洒琢,對網頁的某部分進行更新秧秉。傳統(tǒng)的網頁(不使用 AJAX)如果需要更新內容,必須重載整個網頁頁面衰抑。
Ajax 具有以下優(yōu)點和缺點:
- 優(yōu)點
- 無需刷新頁面象迎,用戶體驗好;
- 異步與服務器通信呛踊,不影響主進程砾淌,響應更迅速;
- 可以把部分服務器的工作放在客戶端的瀏覽器完成谭网,減輕服務器壓力汪厨,減少冗余請求和響應;
- Ajax 是前端開發(fā)的標準化技術愉择,無需插件支持劫乱,跨平臺性能好;
- 缺點
- Ajax 請求不修改瀏覽器歷史記錄锥涕,因此不支持前進后退功能衷戈;
- Ajax 暴露了過多和服務器交互的細節(jié);
- 破壞了程序的異常機制层坠,容易調試脱惰;
- 不利于搜索引擎抓取信息;
同源策略
同源策略是Netscape提出的一個著名的安全策略窿春,它是指同一個“源頭”的數(shù)據(jù)可以自由訪問拉一,但不同源的數(shù)據(jù)相互之間都不能訪問。我們試想一下以下幾種情況:
- 我們打開了一個天貓并且登錄了自己的賬號旧乞,這時我們再打開一個天貓的商品蔚润,我們不需要再進行一次登錄就可以直接購買商品,因為這兩個網頁是同源的尺栖,可以共享登錄相關的 cookie 或 localStorage 數(shù)據(jù)嫡纠;
- 如果你正在用支付寶或者網銀,同時打開了一個不知名的網頁,如果這個網頁可以訪問你支付寶或者網銀頁面的信息除盏,就會產生嚴重的安全的問題叉橱。顯然瀏覽器不允許這樣的事情發(fā)生;
- 想必你也有過同時登陸好幾個 qq 賬號的情況者蠕,如果同時打開各自的 qq 空間瀏覽器會有一個小號模式窃祝,也就是另外再打開一個窗口專門用來打開第二個 qq 賬號的空間。
很明顯踱侣,第1個和第3個例子中粪小,不同的天貓商店和 qq 空間屬于同源,可以共享登錄信息抡句。qq 為了區(qū)別不同的 qq 的登錄信息探膊,重新打開了一個窗口,因為瀏覽器的不同窗口是不能共享信息的待榔。而第2個例子中的支付寶逞壁、網銀、不知名網站之間是非同源的锐锣,所以彼此之間無法訪問信息腌闯,如果你執(zhí)意想請求數(shù)據(jù),會提示異常:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
那么什么是同源的請求呢刺下?同源請求要求被請求資源頁面和發(fā)出請求頁面滿足3個相同:
協(xié)議相同
域名相同
端口相同
簡單理解一下:
/*以下兩個數(shù)據(jù)非同源,因為協(xié)議不同*/
http://www.abc123.com.cn/item/a.js
https://www.abc123.com.cn/item/a.js
/*以下兩個數(shù)據(jù)非同源稽荧,因為域名不同*/
http://www.abc123.com.cn/item/a.js
http://www.abc123.com/item/a.js
/*以下兩個數(shù)據(jù)非同源橘茉,因為主機名不同*/
http://www.abc123.com.cn/item/a.js
http://item.abc123.com.cn/item/a.js
/*以下兩個數(shù)據(jù)非同源,因為協(xié)議不同*/
http://www.abc123.com.cn/item/a.js
http://www.abc123.com.cn:8080/item/a.js
/* 以下兩個數(shù)據(jù)非同源姨丈,域名和 ip 視為不同源
* 這里應注意畅卓,ip和域名替換一樣不是同源的
* 假設www.abc123.com.cn解析后的 ip 是 195.155.200.134
*/
http://www.abc123.com.cn/
http://195.155.200.134/
/*以下兩個數(shù)據(jù)同源*/ /* 這個是同源的*/
http://www.abc123.com.cn/source/a.html
http://www.abc123.com.cn/item/b.js
Ajax
Ajax在編寫時一共4個步驟:
- 創(chuàng)建 xhr 對象
- 設置傳輸?shù)刂?/li>
- 設置回調函數(shù)
- 發(fā)送數(shù)據(jù)
常見的發(fā)送方式有 GET 和 POST,除此之外還有 HEAD, DELETE, TRACE, PUT, CONNECT, OPTIONS和 PATCH等蟋恬,這里只舉例前兩個 GET 和 POST翁潘。
例如根據(jù)姓名查詢一個人的信息并寫在div#output中
//GET 方法
function search(name, fun){
var xhr = new XMLHttpRequest();
var url = "search.php?name=" + window.encodeURIComponent(name) + "&t=" + Math.random();
xhr.open("GET", url);
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
var data = JSON.parse(xhr.responseText); //獲取了 JSON 字符串
fun(data);
}
}
}
function show(data){
this.innerHTML = "姓名:" + data.name + "<br />性別:" + data.gender + "<br />年齡:" + data.age + "<br />地址:" + data.address + "<br />電話:" + data.tel;
}
var output = document.getElementById("output");
search("李華", show.bind(output));
//服務器端 search.php
<?php
$name = $_GET[name];
//模擬數(shù)據(jù)查詢結果
echo '{"name":"' . $name .'","age":18,"gender":"男","tel":"13211112222","address":"北京市海淀區(qū)xxxxxxxx"}';
?>
//POST方法
function search(name, fun){
var xhr = new XMLHttpRequest();
var url = "search.php";
var para = "name=" + window.encodeURIComponent(name) + "&t=" + Math.random();
xhr.open("POST", url);
//POST方式下,必須把 Content-Type 設置為application/x-www-form-urlencoded
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
console.log(xhr.responseText);
var data = JSON.parse(xhr.responseText); //獲取了 JSON 字符串
fun(data);
}
}
xhr.send(para);
}
function show(data){
this.innerHTML = "姓名:" + data.name + "<br />性別:" + data.gender + "<br />年齡:" + data.age + "<br />地址:" + data.address + "<br />電話:" + data.tel;
}
var output = document.getElementById("output");
search("李華", show.bind(output));
//服務器端 search.php
<?php
$name = $_POST[name];
//模擬數(shù)據(jù)查詢結果
echo '{"name":"' . $name .'","age":18,"gender":"男","tel":"13211112222","address":"北京市海淀區(qū)xxxxxxxx';
?>
上述代碼的 jQuery 寫法:
//GET 方式
function search(name, fun){
var url = "search.php?name=" + window.encodeURIComponent(name) + "&t=" + Math.random();
$.get(url, fun);
}
function show(data){
data = JSON.parse(data);
this.innerHTML = "姓名:" + data.name + "<br />性別:" + data.gender + "<br />年齡:" + data.age + "<br />地址:" + data.address + "<br />電話:" + data.tel;
}
var output = document.getElementById("output");
search("李華", show.bind(output));
//POST 方式
function search(name, fun){
var url = "search.php";
var obj = {};
obj.name = name;
obj.t = Math.random();
$.post(url, obj, fun);
}
function show(data){
data = JSON.parse(data);
this.innerHTML = "姓名:" + data.name + "<br />性別:" + data.gender + "<br />年齡:" + data.age + "<br />地址:" + data.address + "<br />電話:" + data.tel;
}
var output = document.getElementById("output");
search("李華", show.bind(output));
Ajax常見問題
緩存問題
細心一些可以發(fā)現(xiàn)歼争,上面發(fā)送請求的數(shù)據(jù)中加入了一個隨機數(shù) t拜马。因為有時服務器更新的了數(shù)據(jù)后,我們再一次執(zhí)行 Ajax 請求不能顯示新的結果沐绒,這是由于 js 為了加速俩莽,頁面會使用緩存保持當前調用的相同鏈接。我們加了一個隨機數(shù)以后乔遮,每次請求不同扮超,瀏覽器就不會使用緩存數(shù)據(jù)了。
中文亂碼問題
返回的中文數(shù)據(jù)亂碼是因為 js 頁面和action頁面中使用了不同的編碼方式導致的〕鏊ⅲ可以有以下2中方式解決(瀏覽器 html 文件是 urf-8 編碼的):
- 對請求數(shù)據(jù)字段進行2次 encodeURI 編碼璧疗,服務器獲取數(shù)據(jù)后做一次 UTF-8 轉碼
- 對請求數(shù)據(jù)字段進行1次 encodeURI 編碼,服務器獲取數(shù)據(jù)后做一次 ISO-8859-1 轉換 和一次 UTF-8 轉碼
<small>tips: 考慮到兼容性馁龟,第1個方法更好</small>
兼容性問題
之前的代碼并沒有按兼容性的格式書寫崩侠,不過 Ajax 的兼容也不難,主要表現(xiàn)在 XMLHTTPRequest對象獲取環(huán)節(jié):
var xhr;
if(XMLHttpRequest){
xhr = new XMLHttpRequest(); //chrome, safari, opera, firefox
} else if(ActionXObject){
try{
xhr = new ActionXObject("Msxml2.XMLHTTP"); //IE 中 Msxml 插件
}catch(e){
xhr = new ActionXObject("Microsoft.XMLHTTP"); //IE
}
}
GET和POST方式對比
--- | GET | POST |
---|---|---|
后退/刷新 | 無害 | 數(shù)據(jù)會重新提交 |
書簽 | 可藏為書簽 | 無法藏為書簽 |
緩存 | 可以緩存 | 不可以緩存 |
MIME類型 | application/x-www-from-urlencode | application/x-www-from-urlencode或 multipart/form-data (二進制為多重編碼 |
歷史記錄 | 參數(shù)保留在歷史記錄中 | 參數(shù)不會留在歷史記錄 |
數(shù)據(jù)長度 | URL最長2048個字符(2kB) | 無限 |
數(shù)據(jù)類型 | ASCII字符 | 無限 |
安全性 | 差 | 較 |
可見性 | 數(shù)據(jù)可見 | 數(shù)據(jù)不可見 |
跨域數(shù)據(jù)訪問
JSONP
這里需要強調的是屁柏,jsonp不屬于Ajax的部分啦膜,它只是吧url放入script標簽中實現(xiàn)的數(shù)據(jù)傳輸,主要優(yōu)點是不受同源策略限制淌喻。由于一般庫也會把它和Ajax封裝在一起僧家,所以這里放在一起討論。下面是一個jsonp的例子(實現(xiàn)功能:輸入手機號碼查詢歸屬地和運營商):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>兼容問題</title>
</head>
<body>
<form>
<input type="text" name="tel" id="tel" />
<input type="button" value="search" id="search"/>
<br/>
</form>
<div id="output"></div>
</body>
<script>
function jsonpCallback(data) {
document.getElementById('output').innerHTML = data.province + " " + data.catName;
}
document.getElementById('search').onclick = function(){
var num = document.getElementById('tel').value;
if(/^1[34578]\d{9}$/.test(num)){
var url = "http://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=" + num + "t=" + Math.random() + "&callback=jsonpCallback";
var JSONP=document.createElement("script");
JSONP.type="text/javascript";
JSONP.src= url;
document.getElementsByTagName("head")[0].appendChild(JSONP);
} else {
alert("您輸入的手機號有誤")
}
};
</script>
</html>
上述代碼的全部js部分可以用jQuery實現(xiàn)裸删,如下:
function jsonpCallback(data) {
$('#output').text(data.province + " " + data.catName);
}
$('#search').click(function(){
var num = $('#tel').val();
if(/^1[34578]\d{9}$/.test(num)){
var url = "http://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=" + num" + "t=" + Math.random();
$.ajax({
url: url,
type: 'GET',
dataType: 'JSONP', // 處理Ajax跨域問題(本質已不是Ajax)
success: function(data){
$('#output').text(data.province + " " + data.catName);
}
});
} else {
alert("您輸入的手機號有誤")
}
});
其他 Ajax 參數(shù)及方法
- javascript
//屬性
xhr.responseText; //從服務器返回的字符串數(shù)據(jù)
xhr.responseXML; //從服務器返回的 XML 數(shù)據(jù)
xhr.status; //服務器相應狀態(tài)
xhr.readyState; //0: 請求未初始化; 1: 已建立連接; 2: 請求已接收; 3: 請求處理中; 4: 響應已就緒
xhr.timeout; //指定多少毫秒后超時八拱,長整型
xhr.upload; //獲取上傳進度
xhr.withCredentials; //是否可以跨源,boolean 型涯塔,默認 false
//方法
xhr.getResponseHeader('connection'); //獲取指定頭信息
xhr.getAllResponseHeaders(); //獲全部定頭信息
xhr.open("METHOD", url, isAsyn); //open方法有3個參數(shù)肌稻,最后一個參數(shù)是 Boolean 型,表示是否異步匕荸,默認為 true
xhr.abort(); //終止請求爹谭,置xhr.readyState為0,但不觸發(fā)onreadystatechange
xhr.overrideMimeType() //強制重寫 http 頭的 MIME 類型
//事件
XMLHttpRequestEventTarget.onreadystatechange //在xhr.readyState屬性改變時觸發(fā)
XMLHttpRequestEventTarget.ontimeout //在響應超時時觸發(fā)
XMLHttpRequestEventTarget.onabort //當請求失敗時調用該方法
XMLHttpRequestEventTarget.onerror //當請求發(fā)生錯誤時調用該方法
XMLHttpRequestEventTarget.onload //當一個HTTP請求正確加載出內容后返回時調用榛搔。
XMLHttpRequestEventTarget.onloadstart //當一個HTTP請求開始加載數(shù)據(jù)時調用诺凡。
XMLHttpRequestEventTarget.onloadend //當內容加載完成践惑,不管失敗與否腹泌,都會調用該方法
XMLHttpRequestEventTarget.onprogress //間歇調用該方法用來獲取請求過程中的信息。
注:關于 xhr.status 可能的返回值尔觉,詳見 http狀態(tài)碼
jQuery 中的 Ajax 方法
ajax 靜態(tài)方法
$.ajax({options}) //發(fā)起一個 ajax 請求
options 常用以下屬性設置:url, method("GET"/"POST"), crossDomain, accepts(可接受的類型), dataType, cache, contentType(編碼格式), success, error等
$.ajaxSetup({options}); //options同上凉袱,設置 ajax 默認參數(shù),不建議使用
$.post(url, data, success, datatype); //發(fā)起一個 POST 請求 data為傳遞參數(shù)(可選), success(reponseText, statusText, xhr) 為成功時的回調函數(shù)(可選), datatype(xml/html/script/json/jsonp/text,可選)
$.get(url, data, success, datatype); //發(fā)起一個 GET 請求, 參數(shù)同上
$.getScript(url, data, success) //以 GET 請求獲取一個 JS 文件并執(zhí)行侦铜,參數(shù)含義同上
$.getJSON(url, data, success) //以 GET 請求獲取一個 JSON 字符串专甩,參數(shù)含義同上
ajax 動態(tài)方法
$().ajaxComplete(function(){}); //注冊Ajax請求完成時要調用的處理程序
$().ajaxError(function(){}); //注冊要在Ajax請求完成時遇到錯誤而調用的處理程序
$().ajaxSend(function(){}); //附加要在發(fā)送Ajax請求之前執(zhí)行的函數(shù)
$().ajaxStart(function(){}); //注冊在第一個Ajax請求開始時要調用的處理程序
$().ajaxStop(function(){}); //注冊要在所有Ajax請求完成后調用的處理程序
$().ajaxSuccess(function(){}); //附加要在Ajax請求成功完成時執(zhí)行的函數(shù)
$().load(url, data, callback); //返回某 url 的數(shù)據(jù),data為傳遞參數(shù)(可選), callback(reponseText, statusText, xhr) 回調函數(shù)(可選)
其他相關方法
$.param(obj); //將對象轉化為一個 url 參數(shù)列表
$(form).serialize(); //表單數(shù)據(jù)序列化為 url 參數(shù)列表
$(form).serializeArray(); //同上钉稍,但返回 JSON 串
簡單封裝 Ajax 相關方法
簡單模仿 jQuery 中 $.ajax()
方法
(function(){
// Ajax 選項
var options = {
type: "GET", //提交方式
url: "", //路徑
params: {}, //請求參數(shù)
dataType: "text", //內容類型
success: function(){}, //回調函數(shù)
error: function(){}
};
//獲取 XMLHTTPRequest 對象
var createRequest = function(){
var xmlhttp;
if(xmlhttp.XMLHttpRequest){
xmlhttp = new XMLHttpRequest();
}
else{
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
}
if(xmlhttp.overrideMimeType){
xmlhttp.overrideMimeType('text/xml'); //修改 MIME 類型
}
return xmlhttp;
},
// 設定 Ajax 選項
var setOptions = function(newOptions){
for(var prop in newOptions){
if(newOptions.hasOwnProperty(prop)){
this.option[prop] = newOptions[prop];
}
}
},
//格式化參數(shù)列表
var formatParameters = function(){
var paramsArr = [];
var params = this.options.params;
for(var prop in params){
if(params.hasOwnProperty(prop)){
paramsArr.push(prop + "=" + encodeURIComponent(params[prop]));
}
}
return paramsArr.join('&');
},
//預處理并調用相應函數(shù)
var readystatechange = function(xmlhttp){
var returnValue;
if(xmlhttp.readyState == 4 && xmlhttp.status == 200){
switch(this.options.dataType){
case 'xml':
returnValue = xmlhttp.responseXML;
break;
case 'json':
returnVaue = xmlhttp.responseText;
if(returnValue){
returnValue = eval("(" + returnValue + ")");
}
break;
default:
returnVaue = xmlhttp.responseText;
break;
}
if(returnValue){
this.options.success(returnValue);
}
else{
this.options.success();
}
} else{
this.options.error();
}
},
//發(fā)送請求配深,也就是$.ajax()函數(shù)
var request = function(options){
// var ajaxObj = this;
var xmlhttp = this.createRequest();
this.setOptions(options);
xmlhttp.onreadystatechange = this.readystatechange.bind(null, xmlhttp);
var formatParams = this.formatParameters();
var type = this.options.type;
var url = this.options.url;
if("GET" === type.toUpperCase()){
url += "?" + formatParameters;
}
xmlhttp.open(type, url, true);
if("GET" === type.toUpperCase()){
xmlhttp.send();
} else if("POST" === type.toUpperCase()){
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlhttp.send(formatParameters);
}
}
window.$.ajax = request; //暴露方法到閉包外面去
})();