1. 程式人生 > >jQuery原始碼分析系列(34) : Ajax

jQuery原始碼分析系列(34) : Ajax

上一章大概講了前置過濾器和請求分發器的作用,這一章主要是具體分析每種對應的處理方式

$.ajax()呼叫不同型別的響應,被傳遞到成功處理函式之前,會經過不同種類的預處理(prefilters)。 預處理的型別取決於由更加接近預設的Content-Type響應,但可以明確使用dataType選項進行設定。如果提供了dataType選項, 響應的Content-Type頭資訊將被忽略。

有效的資料型別是text, html, xml, json,jsonp,和 script.

dataType:預期伺服器返回的資料型別。如果不指定,jQuery 將自動根據 HTTP 包 MIME 資訊來智慧判斷,比如XML MIME型別就被識別為XML。在1.4中,JSON就會生成一個JavaScript物件,而script則會執行這個指令碼。隨後伺服器端返回的資料會根據這個值解析後,傳遞給回撥函式。可用值:

script 型別

$.ajax({
    type     : "GET",
    url      : "test.js",
    dataType : "script",
    complete: function(jqXHR, status) {
        console.log(jqXHR, status)
    }
});

根據API的說明可知,如果dataType型別為script的時候,需要處理

1 執行指令碼

2 內容當作純文字返回

3 預設情況下不會通過在URL中附加查詢字串變數 "_=[TIMESTAMP]" 進行自動快取結果,除非設定了cache

引數為true

4 在遠端請求時(不在同一個域下),所有POST請求都將轉為GET請求。(因為將使用DOM的script標籤來載入)

針對上述四點,我們看看處理的流程

inspectPrefiltersOrTransports(prefilters, s, options, jqXHR);

此時的dataType型別就會經過對應的預處理ajaxPrefilter("script")

cache (預設: true, dataType為"script"和"jsonp"時預設為false)

jQuery.ajaxPrefilter("script", function(s) {
    
if (s.cache === undefined) { s.cache = false; } if (s.crossDomain) { s.type = "GET"; } });

預處理的處理就是將其快取為設定為 false ,瀏覽器將不快取此頁面

這將在請求的URL的查詢字串中追加一個時間戳引數,以確保每次瀏覽器下載的指令碼被重新請求

工作原理是在GET請求引數中附加"_={timestamp}"在請求的地址後面加一個時間戳

if (s.cache === false) {
    s.url = rts.test(cacheURL) ?
    // If there is already a '_' parameter, set its value
    cacheURL.replace(rts, "$1_=" + ajax_nonce++) :
    // Otherwise add one to the end
    cacheURL + (ajax_rquery.test(cacheURL) ? "&" : "?") + "_=" + ajax_nonce++;
}

此時的 s.url = "test.js?_=1402362401890"; 

該引數不是其他請求所必須的,除了在IE8中,當一個POST請求一個已經用GET請求過的URL

json jsonp 型別

  • "json":  把響應的結果當作 JSON 執行,並返回一個JavaScript物件。在 jQuery 1.4 中,JSON 格式的資料以嚴格的方式解析,如果格式有錯誤,jQuery都會被拒絕並丟擲一個解析錯誤的異常。(見json.org的更多資訊,正確的JSON格式。)
  • 如果指定的是json,響應結果作為一個物件,在傳遞給成功處理函式之前使用jQuery.parseJSON進行解析。 解析後的JSON物件可以通過該jqXHR物件的responseJSON屬性獲得的。
  • json的處理只要是在ajaxConvert方法中把結果給轉換成需要是json格式,這是後面的內容,這裡主要研究下jsonp的預處理

JSONP是一個非官方的協議,它允許在伺服器端整合Script tags返回至客戶端,通過javascript callback的形式實現跨域訪問(這僅僅是JSONP簡單的實現形式)。JSON系統開發方法是一種典型的面向資料結構的分析和設計方法,以活動為中心,一連串的活動的順序組合成一個完整的工作程序。

跨域這個問題的產生根本原因是瀏覽器的同源策略限制,理解同源策略的限制同源策略是指阻止程式碼獲得或者更改從另一個域名下獲得的檔案或者資訊。也就是說我們的請求地址必須和當前網站的地指相同。同源策略通過隔離來實現對資源的保護。這個策略的歷史非常悠久從Netscape Navigator 2.0時代就開始了。

  • 解決這個限制的一個相對簡單的辦法就是在伺服器端傳送請求,伺服器充當一個到達第三方資源的代理中繼。雖然是用廣泛但是這個方法卻不夠靈活。
  • 另一個辦法就是使用框架(frames),將第三方站點的資源包含進來,但是包含進來的資源同樣要受到同源策略的限制。
  • 有一個很巧妙的辦法就是在頁面中使用動態程式碼元素,程式碼的源指向服務地址並在自己的程式碼中載入資料。當這些程式碼載入執行的時候,同源策略就不會起到限制。但是如果程式碼試圖下載檔案的時候執行還是會失敗,幸運的是,我們可以使用JSON(JavaScript Object Notation)來改進這個應用

JSON和JSONP

與XML相比,JSON是一個輕量級的資料交換格式。JSON對於JavaScript開發人員充滿魅力的原因在於JSON本身就是Javascript中的物件。

例如一個ticker物件

var ticker = {symbol:'IBM',price:100}

而JSON串就是 {symbol:'IBM',price:100}

這樣我們就可以在函式的引數中傳遞JSON資料。我們很容易掌握在函式中使用動態的JSON引數資料,但是我們的目的並不是這個。

通過使我們的函式能夠載入動態的JSON資料,我們就能夠處理動態的資料,這項技術叫做 Dynamic Javascript Insertion。

index.html 中

function showPrice(data){  
    alert("Symbol:" + data.symbol + ", Price:" + data.price);  
}

然後動態載入ticker.js指令碼

var data = {symbol:'IBM', price:100};  
showPrice(data);

程式碼通過動態加入Javascript程式碼,來執行函式載入資料

正如之前提到過的,同源策略對於動態插入的程式碼不適用。也就是你可以從不同的域中載入程式碼,來執行在他們程式碼中的JSON資料。

這就是JSONP(JSON with Padding)。注意,使用這種方法時,你必須在頁面中定義回撥函式,就像上例中的showPrice一樣。

我們通常所說的JSONP服務(遠端JSON服務),實際上就是一種擴充套件的支援在使用者定義函式中包含返回資料的能力。這種方法依賴於必須接受一個回撥函式的名字作為引數。

然後執行這個函式,處理JSON資料,並顯示在客戶頁面上。

JSONP的客戶端具體實現:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <script type="text/javascript"src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
    <script>
        alert(jQuery)
    </script>
</head>
<body>
</body>
</html>

通過script是src載入遠端的jQuery毫無疑問是可以正常執行的,所以不難發現Web頁面上呼叫js檔案時則不受是否跨域的影響

當然不僅如此,我們還發現凡是擁有"src"這個屬性的標籤都擁有跨域的能力,比如<script>、<img>、<iframe>等

在進一步我們換成契約式介面

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <script type="text/javascript"src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
    <script type="text/javascript">
        function remoteLoad(data){
                alert(data) //遠端資料
        }
    </script>
</head>
<body>
</body>
</html>


http://code.jquery.com/jquery-1.11.1.min.js 檔案中執行
remoteLoad('載入的資料')

顯而易見OK了,通過載入遠端的指令碼到本地中執行,很好的繞開了跨域的問題了,但是這樣的請求是有問題的,介面是契約式的?

怎麼讓遠端js知道它應該呼叫的本地函式叫什麼名字呢?畢竟是jsonp的服務者都要面對很多服務物件,而這些服務物件各自的本地函式都不相同啊?我們接著往下看。

更進一步增加動態回撥

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
        <script type="text/javascript">
            var remoteLoad= function(data){};
            var url = "http://code.jquery.com/jquery-1.11.1.min.js?code=1111&callback=remoteLoad";
             var script = document.createElement('script');
           script.setAttribute('src', url);
             document.getElementsByTagName('head')[0].appendChild(script); 
       </script>
</head>
<body>

</body>

不再直接把遠端js檔案寫死,而是編碼實現動態查詢,而這也正是jsonp客戶端實現的核心部分,本例中的重點也就在於如何完成jsonp呼叫的全過程。

我們看到呼叫的url中傳遞了一個callback引數則告訴伺服器,我的本地回撥函式叫做remoteLoad,所以請把查詢結果傳入這個函式中進行呼叫。

所以總結其實json的一個核心點:允許使用者傳遞一個callback引數給服務端,然後服務端返回資料時會將這個callback引數作為函式名來包裹住JSON資料,這樣客戶端就可以隨意定製自己的函式來自動處理返回資料了。

基本原理OK了,我們看看jQuery的實現,其實也大同小異

$.ajax({
    url           : "remoteLoad.js",
    dataType      : "jsonp",
    jsonp         : "callback", //傳遞給請求處理程式或頁面的,用以獲得jsonp回撥函式名的引數名(一般預設為:callback)
    jsonpCallback : "Handler", //自定義的jsonp回撥函式名稱,預設為jQuery自動生成的隨機函式名,也可以寫"?",jQuery會自動為你處理資料
    success: function(data) {
        console.log(arguments)
    }
});

jQuery的區別最大的不同的就自動幫你生成回撥函式並把資料取出來供success屬性方法來呼叫,不是傳遞的一個回撥控制代碼

篇幅比較長了了 下章再合併講解內部實現及請求分發器