Ajax與異步數據傳輸

基本概念

Ajax 全稱是異步的 JavaScript 和 XML 。 通過在后臺與服務器進行少量數據交換,AJAX 可以使網頁實現異步更新。這意味著可以在不重新加載整個網頁的情況下,對網頁的某部分進行更新。傳統的網頁(不使用 AJAX)如果需要更新內容,必須重載整個網頁頁面。

Ajax 具有以下優點和缺點:

  • 優點
  1. 無需刷新頁面,用戶體驗好;
  2. 異步與服務器通信,不影響主進程,響應更迅速;
  3. 可以把部分服務器的工作放在客戶端的瀏覽器完成,減輕服務器壓力,減少冗余請求和響應;
  4. Ajax 是前端開發的標準化技術,無需插件支持,跨平臺性能好;
  • 缺點
  1. Ajax 請求不修改瀏覽器歷史記錄,因此不支持前進后退功能;
  2. Ajax 暴露了過多和服務器交互的細節;
  3. 破壞了程序的異常機制,容易調試;
  4. 不利于搜索引擎抓取信息;

同源策略

同源策略是Netscape提出的一個著名的安全策略,它是指同一個“源頭”的數據可以自由訪問,但不同源的數據相互之間都不能訪問。我們試想一下以下幾種情況:

  1. 我們打開了一個天貓并且登錄了自己的賬號,這時我們再打開一個天貓的商品,我們不需要再進行一次登錄就可以直接購買商品,因為這兩個網頁是同源的,可以共享登錄相關的 cookie 或 localStorage 數據;
  2. 如果你正在用支付寶或者網銀,同時打開了一個不知名的網頁,如果這個網頁可以訪問你支付寶或者網銀頁面的信息,就會產生嚴重的安全的問題。顯然瀏覽器不允許這樣的事情發生;
  3. 想必你也有過同時登陸好幾個 qq 賬號的情況,如果同時打開各自的 qq 空間瀏覽器會有一個小號模式,也就是另外再打開一個窗口專門用來打開第二個 qq 賬號的空間。

很明顯,第1個和第3個例子中,不同的天貓商店和 qq 空間屬于同源,可以共享登錄信息。qq 為了區別不同的 qq 的登錄信息,重新打開了一個窗口,因為瀏覽器的不同窗口是不能共享信息的。而第2個例子中的支付寶、網銀、不知名網站之間是非同源的,所以彼此之間無法訪問信息,如果你執意想請求數據,會提示異常:

No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.

那么什么是同源的請求呢?同源請求要求被請求資源頁面和發出請求頁面滿足3個相同:

協議相同
域名相同
端口相同

簡單理解一下:

/*以下兩個數據非同源,因為協議不同*/
http://www.abc123.com.cn/item/a.js
https://www.abc123.com.cn/item/a.js

/*以下兩個數據非同源,因為域名不同*/
http://www.abc123.com.cn/item/a.js
http://www.abc123.com/item/a.js

/*以下兩個數據非同源,因為主機名不同*/
http://www.abc123.com.cn/item/a.js
http://item.abc123.com.cn/item/a.js

/*以下兩個數據非同源,因為協議不同*/
http://www.abc123.com.cn/item/a.js
http://www.abc123.com.cn:8080/item/a.js

/* 以下兩個數據非同源,域名和 ip 視為不同源
 * 這里應注意,ip和域名替換一樣不是同源的
 * 假設www.abc123.com.cn解析后的 ip 是 195.155.200.134
 */
http://www.abc123.com.cn/
http://195.155.200.134/

/*以下兩個數據同源*/                               /* 這個是同源的*/
http://www.abc123.com.cn/source/a.html
http://www.abc123.com.cn/item/b.js

Ajax

Ajax在編寫時一共4個步驟:

  1. 創建 xhr 對象
  2. 設置傳輸地址
  3. 設置回調函數
  4. 發送數據

常見的發送方式有 GET 和 POST,除此之外還有 HEAD, DELETE, TRACE, PUT, CONNECT, OPTIONS和 PATCH等,這里只舉例前兩個 GET 和 POST。

例如根據姓名查詢一個人的信息并寫在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];
 //模擬數據查詢結果
 echo '{"name":"' . $name .'","age":18,"gender":"男","tel":"13211112222","address":"北京市海淀區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];
 //模擬數據查詢結果
 echo '{"name":"' . $name .'","age":18,"gender":"男","tel":"13211112222","address":"北京市海淀區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常見問題

緩存問題

細心一些可以發現,上面發送請求的數據中加入了一個隨機數 t。因為有時服務器更新的了數據后,我們再一次執行 Ajax 請求不能顯示新的結果,這是由于 js 為了加速,頁面會使用緩存保持當前調用的相同鏈接。我們加了一個隨機數以后,每次請求不同,瀏覽器就不會使用緩存數據了。

中文亂碼問題

返回的中文數據亂碼是因為 js 頁面和action頁面中使用了不同的編碼方式導致的。可以有以下2中方式解決(瀏覽器 html 文件是 urf-8 編碼的):

  1. 對請求數據字段進行2次 encodeURI 編碼,服務器獲取數據后做一次 UTF-8 轉碼
  2. 對請求數據字段進行1次 encodeURI 編碼,服務器獲取數據后做一次 ISO-8859-1 轉換 和一次 UTF-8 轉碼
    <small>tips: 考慮到兼容性,第1個方法更好</small>

兼容性問題

之前的代碼并沒有按兼容性的格式書寫,不過 Ajax 的兼容也不難,主要表現在 XMLHTTPRequest對象獲取環節:

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
后退/刷新 無害 數據會重新提交
書簽 可藏為書簽 無法藏為書簽
緩存 可以緩存 不可以緩存
MIME類型 application/x-www-from-urlencode application/x-www-from-urlencode或 multipart/form-data (二進制為多重編碼
歷史記錄 參數保留在歷史記錄中 參數不會留在歷史記錄
數據長度 URL最長2048個字符(2kB) 無限
數據類型 ASCII字符 無限
安全性
可見性 數據可見 數據不可見

跨域數據訪問

JSONP

這里需要強調的是,jsonp不屬于Ajax的部分,它只是吧url放入script標簽中實現的數據傳輸,主要優點是不受同源策略限制。由于一般庫也會把它和Ajax封裝在一起,所以這里放在一起討論。下面是一個jsonp的例子(實現功能:輸入手機號碼查詢歸屬地和運營商):

<!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實現,如下:

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 參數及方法

  • javascript
//屬性
xhr.responseText;   //從服務器返回的字符串數據
xhr.responseXML;   //從服務器返回的 XML 數據
xhr.status;    //服務器相應狀態
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個參數,最后一個參數是 Boolean 型,表示是否異步,默認為 true
xhr.abort();   //終止請求,置xhr.readyState為0,但不觸發onreadystatechange
xhr.overrideMimeType()   //強制重寫 http 頭的 MIME 類型
//事件
XMLHttpRequestEventTarget.onreadystatechange   //在xhr.readyState屬性改變時觸發
XMLHttpRequestEventTarget.ontimeout   //在響應超時時觸發
XMLHttpRequestEventTarget.onabort   //當請求失敗時調用該方法
XMLHttpRequestEventTarget.onerror   //當請求發生錯誤時調用該方法
XMLHttpRequestEventTarget.onload   //當一個HTTP請求正確加載出內容后返回時調用。
XMLHttpRequestEventTarget.onloadstart   //當一個HTTP請求開始加載數據時調用。
XMLHttpRequestEventTarget.onloadend   //當內容加載完成,不管失敗與否,都會調用該方法
XMLHttpRequestEventTarget.onprogress   //間歇調用該方法用來獲取請求過程中的信息。

注:關于 xhr.status 可能的返回值,詳見 http狀態碼

jQuery 中的 Ajax 方法

ajax 靜態方法

$.ajax({options})    //發起一個 ajax 請求
options 常用以下屬性設置:url, method("GET"/"POST"), crossDomain, accepts(可接受的類型), dataType, cache, contentType(編碼格式), success, error等
$.ajaxSetup({options});    //options同上,設置 ajax 默認參數,不建議使用
$.post(url, data, success, datatype);    //發起一個 POST 請求 data為傳遞參數(可選), success(reponseText, statusText, xhr)  為成功時的回調函數(可選), datatype(xml/html/script/json/jsonp/text,可選)
$.get(url, data, success, datatype);    //發起一個 GET 請求, 參數同上
$.getScript(url, data, success)    //以 GET 請求獲取一個 JS 文件并執行,參數含義同上
$.getJSON(url, data, success)    //以 GET 請求獲取一個 JSON 字符串,參數含義同上

ajax 動態方法

$().ajaxComplete(function(){});    //注冊Ajax請求完成時要調用的處理程序
$().ajaxError(function(){});    //注冊要在Ajax請求完成時遇到錯誤而調用的處理程序
$().ajaxSend(function(){});    //附加要在發送Ajax請求之前執行的函數
$().ajaxStart(function(){});    //注冊在第一個Ajax請求開始時要調用的處理程序
$().ajaxStop(function(){});    //注冊要在所有Ajax請求完成后調用的處理程序
$().ajaxSuccess(function(){});    //附加要在Ajax請求成功完成時執行的函數
$().load(url, data, callback);    //返回某 url 的數據,data為傳遞參數(可選), callback(reponseText, statusText, xhr) 回調函數(可選)

其他相關方法

$.param(obj);    //將對象轉化為一個 url 參數列表
$(form).serialize();    //表單數據序列化為 url 參數列表
$(form).serializeArray();    //同上,但返回 JSON 串

簡單封裝 Ajax 相關方法

簡單模仿 jQuery 中 $.ajax() 方法

(function(){
  // Ajax 選項
  var options = {
    type: "GET",   //提交方式
    url: "",    //路徑
    params: {},   //請求參數
    dataType: "text",   //內容類型
    success: function(){},   //回調函數
    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];
      }
    }
  },

  //格式化參數列表
  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('&');
  },

  //預處理并調用相應函數
  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();
    }
  },

  //發送請求,也就是$.ajax()函數
  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;  //暴露方法到閉包外面去
})();
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容

  • AJAX 原生js操作ajax 1.創建XMLHttpRequest對象 var xhr = new XMLHtt...
    碧玉含香閱讀 3,253評論 0 7
  • 在線閱讀 http://interview.poetries.top[http://interview.poetr...
    前端進階之旅閱讀 114,552評論 24 450
  • <a name='html'>HTML</a> Doctype作用?標準模式與兼容模式各有什么區別? (1)、<...
    clark124閱讀 3,539評論 1 19
  • 所有正面管教鼓勵咨詢的信息是從初中同學王嵐的朋友圈知道的,她的姐姐就是一位非常優秀的咨詢師。 其實我并不是很關注朋...
    不zuo不die閱讀 301評論 0 0