JavaScipt 數據交互

標準的w3c直接提供了XMLHttpRequest方法,咱們主要站在設計的角度來理解,如何設計出低耦合高內聚的代碼jquery對Ajax的處理主要體如今對瀏覽器兼容,數據的處理過濾以及各類事件的封裝上,主要有幾個部分的擴展javascript

提供快捷接口,提供底層接口,提供數據序列化,提供全局Ajax事件處理php

給document綁定ajaxStart,ajaxComplete回調事件,trgger綁定一個點擊事件,經過click觸發事件發送一個ajax請求,而且經過complete,done,ajaxStart,ajaxComplete返回狀態回調。css

$(document).ajaxStart(function(){html

  console.info(arguements)java

}).ajaxComplete(function(){jquery

  $(".log").text("");git

});github

$(".trigger").click(function(){ajax

  //發送ajax請求express

  $.ajax({

    url:"index.html",

    context:document.body,

    complete:function(){

      console.info(this);

    }

  }).done(function(){

    console.info(this);

  });

});

這裏實現比較特別的地方,針對ajax提供3種回調方式:

1,內部回調beforeSend,error,dataFilter,success和complete等

2,外部的done,fail,when,always等

3,全局document上都能捕獲到ajax的每一步的回調通知,ajaxStart,ajaxStop,ajaxComplete(),ajaxError(),ajaxSuccess(),ajaxSend(),等

針對ajax的請求,每一步的狀態,成功,失敗或者進行中,咱們有3種方式能夠監聽,可是每一種仍是有各自的區別:

1,Ajax的參數回調

2,基於deferred方式的done回調

3,全局的自定義事件的回調

 

接口的設計優劣

設計1:

tAjax({

  url:"index.html",

  complete:function(data){

    console.log(data);

  }

});

若是要實現這種接口調用咱們須要封裝下代碼,把回調經過實參傳遞

var tAjax = function(config){

  var url = config.url;
    var complete = config.complete;
    var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
    xhr.open('post', url);
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            if (xhr.status == 200) {
                complete(xhr.responseText);
            }
        }
    }
    xhr.send();

}

遮這樣的設計能夠看作類型工廠模式的封裝,好處不用多說在工廠模式裏面包含了對象的建立等必要的邏輯,客戶端根據傳參選擇東西的實例化相對的處理,對於庫福段來去除了具體的依賴,固然tAjax也能夠看作一個外觀模式提供的接口,其實就是隱藏了具體的複雜邏輯,提供一個簡單的接口,從而下降耦合。

設計2:

tAjax({

  url:"index.html",

  complete:function(data){

    console.info(data);

  }

}).done(function(data){

  console.info(data);

});

在以前加入了一個done鏈式處理,固然這裏done,實際上是deferred的一個成功處理通知。

var ajax = tAjax({
    url: "php.html",
    complete: function(data) {
        console.log(data)
    }
})
ajax.done(function(){
    //.........
})
var tAjax = function(config) {
   ///參考右圖設計二
    return {
        done: function(ourfn) {
             doneFn = ourfn;
        }
    }
}

咱們返回了一個 done 對象,這裏同樣要是對象,由於鏈式的緣由咱們看外部指定了內部的 done,從而把外部函數給引用到內部的 doneFn 上緩存起來 xhr.staturs 成功後一塊兒執行,固然這種設計是有問題的,若是在 done 以後我再鏈式就確定不行,由於對象的引用錯了,那麼 jQuery 是如何處理?

 

設計3:提供document對象的全局處理

$(document).ajaxComplete(function(){

  console.info("ajax請求成功");

})

tAjax({

  url:"index.html",

  complete:function(data){

    console.info(data);

  }

}).done(function(data){

  console.info(data);

})

這裏的問題就是把ajax內部的事件,返回給全局捕獲了,有點相似css的全局動畫事件

這裏的設計就是發送一個事件給document便可

jQuery利用了trigger自定義事件觸發的globalEventContext.trigger('ajaxComplete',[jqXHR,s]);

具體每一種實如今後面的都會提到,在這裏須要你們的有個總體的印象。

 

設計ajax庫須要考慮的問題

ajax的底層實現都是瀏覽器提供的,因此任何基於api上面的框架或者庫,都只是對於功能的靈活與兼容維護性作出最優的擴展,ajax請求的流程:

1.經過new XMLHttpRequest或其餘的形式(ie生成ajax的對象xhr.

2.經過xhr.open(type,url,async,username,password)的形式創建一個鏈接

3.經過etRequestHeader設定xhr的請求頭部(request header)

4.經過send(data)請求服務器的數據

5.執行在xhr上註冊的onreadstatechange回調處理返回數據。

這幾步中,可能會遇到的問題

跨域,json的格式,dataType,Ajax亂碼問題,頁面緩存,狀態的跟蹤,不一樣平臺的兼容

jQuery主要就是解決上面的問題, 以後就在這個基礎之上進行擴展, jQuery2.3版本的ajax部分源碼大概有1200多行,主要針對ajax模塊的重寫,增長了幾個新的概念,ajax 模塊提供了三個新的方法用於管理,擴展ajax請求,分別是:

前置過濾器jQuery.ajaxPrefilter

請求分發器jQuery.ajaxTransport

類型轉換器ajaxConvert

除此以後還重寫了整個異步隊列處理,加入了 deferred,能夠將任務完成的處理方式與任務自己解耦合,使用 deferreds 對象,多個回調函數能夠被綁定在任務完成時執行,甚至能夠在任務完成後綁定這些回調函數。這些任務能夠是異步的,也能夠是同步的。

 

好比以前提到的:

 

  1. 鏈式反饋 done 與 fail
  2. 分離異步與同步處理,再也不被限制到只有一個成功,失敗或者完成的回調函數了。相反這些隨時被添加的回調函數被放置在一個先進先出的隊列中。
  3. 同時執行多個 Ajax 請求,這個比較複雜一點,原理其實就是 $.get 返回的是一個 deferred 對象,每一個 jQuery 的 Ajax 方法返回值都包含一個 Promise 函數,用來跟蹤異步請求。Promise 函數的返回值是 deferred 對象的一個只讀視圖 Deferreds 經過檢測對象中是否存在 promise() 函數來判斷當前對象是否可觀察。$.when() 會等待全部的 Ajax 請求結束,而後調用經過 .then(), .fail()註冊的回調函數(具體調用哪些回調函數取決於任務的結束狀態)。這些回調函數會按照他們的註冊順序執行。顯而易見,deferred 對象就是 jQuery 的回調函數解決方案,它解決了如何處理耗時操做的問題,對那些操做提供了更好的控制,以及統一的編程接口。

 

Ajax的deferred實現

 

在異步機制這章咱們詳細的分析了 deferred 的設計,其中提供了 deferred.promise 方法就是把普通對象轉化成 deferred 對象了ajax 就是把 deferred 對象給摻進去可讓整個 Ajax 方法變成了一個 deferred 對象,在Ajax方法中返回的是 jqXHR 一個包裝對象,在這個對象裏面混入了全部實現方法。

 

ajax: function(url, options) {
    var jqXHR = {} //ajax對象
    deferred = jQuery.Deferred()
    //轉成deferred對象
    deferred.promise(jqXHR).complete = completeDeferred.add
    return jqXHR
}

 

jQuery.ajax 的版本迭代:

 

  • 從 jQuery 1.5 開始,$.ajax() 返回 XMLHttpRequest(jqXHR)對象,該對象是瀏覽器的原生的 XMLHttpRequest 對象的一個超集。例如,它包含 responseText 和 responseXML 屬性,以及一個 getResponseHeader() 方法。當傳輸機制不是 XMLHttpRequest 時(例如,一個 JSONP 請求腳本,返回一個腳本 tag 時),jqXHR 對象儘量的模擬原生的 XHR 功能。
  • 從 jQuery 1.5.1 開始, jqXHR 對象還包含了overrideMimeType 方法 (它在 jQuery 1.4.x 中是有效的,可是在 jQuery 1.5 中暫時的被移除)。.overrideMimeType() 方法可能用在 beforeSend() 的回調函數中,例如,修改響應的 Content-Type 信息頭:
  • 爲了讓回調函數的名字統一,便於在$.ajax()中使用。jqXHR也提供.error() .success()和.complete()方法。這些方法都帶有一個參數,該參數是一個函數,此函數在 $.ajax()請求結束時被調用,而且這個函數接收的參數,與調用 $.ajax()函數時的參數是一致。這將容許你在一次請求時,對多個回調函數進行賦值,甚至容許你在請求已經完成後,對回調函數進行賦值(若是該請求已經完成,則回調函數會被馬上調用)。

 

爲了向後兼容 XMLHttpRequest ,jqXHR 對象將公開下列屬性和方法:

 

readyState
status
statusText
responseXML and/or responseText 當底層的請求分別做出XML和/或文本響應
setRequestHeader(name, value) 從標準出發,經過替換舊的值爲新的值,而不是替換的新值到舊值
getAllResponseHeaders()
getResponseHeader()
abort()

 


爲了實現以上這些功能,jQuery 在對 jqXHR 作2個處理:

 

  1. 異步隊列 deferred
  2. 回調隊列 Callbacks

 

// Deferreds
deferred = jQuery.Deferred(),
//全部的回調隊列,無論任什麼時候候增長的回調保證只觸發一次
completeDeferred = jQuery.Callbacks("once memory"),

 

給 jqXHR 擴充添加 promise 的屬性和方法,而後添加 complete 方法,這裏用的是回調列表的 add 方法(即添加回調)

 

deferred.promise(jqXHR).complete = completeDeferred.add;

 

 

此時的 jqXHR 就具備了 promise 的一些特性了與 callback 的回調列隊了,固然這裏有個重點,返回了一個只讀的 deferred 對象,若是返回完整的 deferred 對象,那麼外部程序就能隨意的觸發 deferred 對象的回調函數,頗有可能在 AJAX 請求結束前就觸發了回調函數(resolve),這就是與 AJAX 自己的邏輯相違背了。因此爲了不不經意間改變任務的內部流程,咱們應該只返回 deferred 的只讀版本 deferred.promise(),而後把對應的 done 與 fail 改爲別名 success 與 error。

 

jqXHR.success = jqXHR.done;
jqXHR.error   = jqXHR.fail

 

 

 

 

咱們還須要把用戶自定的內部回調函數給註冊到 jqXHR 對象上。

 

// 增長回調隊列
for (i in {
    success  : 1,
    error    : 1,
    complete : 1
}) {
    /**
     * 把參數的回調函數註冊到內部jqXHR對象上,實現統一調用
     * 給ajax對象註冊 回調函數add
     * deferred返回complete,error外部捕獲
     */
    jqXHR[i](s[i]);
}

 

經過一個 for 循環把對應的方法都執行了,具體就是這幾個:

 

  1. jqXHR.success(s.success)  -> jqXHR.done -> jQuery.Callbacks("once memory")
  2. jqXHR.error(s.error)  -> jqXHR.fail -> jQuery.Callbacks("once memory")
  3. jqXHR.complete(s.complete) -> jQuery.Callbacks("once memory").add(s.success

 

 

前置過濾器和請求分發器

jQuery1.5 之後,Ajax 模塊提供了三個新的方法用於管理、擴展 Ajax 請求,分別是:

前置過濾器 jQuery. ajaxPrefilter
請求分發器 jQuery. ajaxTransport,
類型轉換器 ajaxConvert

爲何會出現這幾個新的概念?由於 ajax 在發送的過程還有不少一系列的處理。

  • 類型轉換器將服務端響應的 responseText 或 responseXML,轉換爲請求時指定的數據類型 dataType,若是沒有指定類型就依據響應頭 Content-Type 自動猜想一個。
  • jQuery 的 Ajax 是合併了 jsonp 的處理的,因此針對一些特殊的請求這裏用了一個請求分發器來處理這個邏輯。

具體看看代碼:

jQuery.extend({
    //前置過濾器
    ajaxPrefilter: addToPrefiltersOrTransports(prefilters),
    //請求分發器
    ajaxTransport: addToPrefiltersOrTransports(transports),
});
  1. 可見這 2 個方法是經過私有方法 addToPrefiltersOrTransports(參考右邊代碼一)經過 curry 手段構造的,分別是保持了 prefilters 與 transports 的引用,可見 ajaxPrefilter 就維持了addToPrefiltersOrTransports 返回函數的引用了,這種就是閉包的手法了,這也是 JS 的開發人員都須要掌握的,好處就是合併多個參數,固然由於維持引用代價就是一點點性能消耗。
  2. 固然 jQuery 不是傳遞的簡單類型處理,還能夠傳遞的一個引用類型的回調函數,因此針對 ajaxPrefilter 方法放閉包構件就須要作一些處理了,填充 prefilters 處理器(右側代碼編輯器中的代碼二)。

其實說白了就是把對應的方法制做成函數的形式填充到 prefilters 或者 transports對應的處理包裝對象中,用的時候直接執行,每一個函數都保持着各自的引用,種寫法的好處天然是靈活,易維護,減小代碼量。

因此此時的 prefilters 中的結構能夠是這樣。

prefilters = {
        '*': function() {
            return {
                send: function() {
                },
                callback: function() {
                }
            }
        }
}

前置過濾器和請求分發器在執行時,分別遍歷內部變量 prefilters 和 transports,這兩個變量在 jQuery 加載完畢後當即初始化,從過閉包的方法填充這個 2 個對象。

 

ajaxPrefilter與ajaxTransport

ajaxPrefilter 與 ajaxTransport 都是經過 inspectPrefiltersOrTransports 構建器建立的。

prefilters 中的前置過濾器在請求發送以前、設置請求參數的過程當中被調用,調用 prefilters 的是函數 inspectPrefiltersOrTransports ,巧妙的是 transports 中的請求分發器在大部分參數設置完成後,也經過函數 inspectPrefiltersOrTransports 取到與請求類型匹配的請求分發器。

經過(右邊代碼一)咱們能夠看出來:

  1. 遍歷 structure[dataType] 數組,並執行回調
  2. prefilterOrFactory 爲函數數組元素,執行該函數若是返回的結果 dataTypeOrTransport 是字符串且時 prefilters 且沒有被 inspected 過,就給 options.dataTypes 數組頭部添加該字符串
  3. 繼續遞歸dataTypeOrTransport(當咱們使用 json/jsonp 的時候會返回「script」,因而會執行「script」相關的回調)
  4. 若是是 transport 就返回 dataTypeOrTransport 的假結果


前置過濾器 prefilters

在每一個請求以前被髮送和 $.ajax () 處理它們前處理,設置自定義 Ajax 選項或修改現有選項,簡單的說就是一種 hack 的作法,只是說比起事件的那種 hack 寫的手法實現更爲高明。好比咱們要預過濾器(Prefilters)也能夠被用來修改已經存在的選項。

例如,下面的代理服務器跨域請求 http://mydomain.net/proxy/:

$.ajaxPrefilter( function( options ) {
  if ( options.crossDomain ) {
    options.url = "http://mydomain.net/proxy/" + encodeURIComponent( options.url );
    options.crossDomain = false;
  }
});

若是提供可選的 dataTypes 參數,那麼預濾器(prefilter)將只會對知足指定 dataTypes 的請求有效。例如, 如下僅適用於 JSON 和 script 請求給定的預過濾器:咱們能夠看看針對 prefilters 的方法其實就是 dataType 爲 script,json,jsonp的處理,當咱們動態加載腳本文件好比:

$.ajax({
    type     : "GET",
    url      : "test.js",
    dataType : "script"
});

因此在 inspectPrefiltersOrTransports 方法中 prefilters[script] 能找到對應的處理方法,因此就會執行。例如 script 的 hack,要強制加上處理緩存的特殊狀況和 crossDomain,由於設置 script 的前置過濾器,script 並不必定意思着跨域,跨域未被禁用,強制類型爲 GET,不觸發全局時間。

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

因此 prefilters 就是在特定的環境針對特定的狀況作一些必要的兼容的處理。

請求分發器 transports

請求分發器顧名思義發送請求,那麼底層的 ajax 發送請求是經過 send 方法。

xhr.send();

可是 jQuery 對 send 方法作了拆分,把對應的處理放到了 transports 中了,那麼 transports 對象也是相似前置處理器經過 jQuery.ajaxTransport 構建,例如 script,send,abort 方法返回出 transports 方法。

transport = inspectPrefiltersOrTransports(transports, s, options, jqXHR);

從源碼中能夠看到 transport 是一個對象,它提供了兩種方法,send 和 abort,內部使用由 $.ajax() 發出請求。transport 是最高級的方法用來加強 $.ajax() 而且應僅做爲當預過濾器(prefilters)和轉換器(converters)沒法知足你的需求的時候的最後的手段。因爲每一個請求須要有本身的傳輸(transport)對象實例,傳輸不能直接註冊。所以,你應該提供一個函數代替返回傳輸(transport)。

 

預處理script類型

$.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則會執行這個腳本,隨後服務器返回的數據會根據這個值解析後,傳遞給回調函數。

sctipt類型

$.ajax({

  type:"GET",

  url:"test.js",

  dataType:"script",

  complete:function(jqXHR,status){

    console.info(jqXHR,status);

  }

});

若是dataType類型爲script的時候,須要處理:

1,執行腳本

2,內容當作純文本你返回

3,默認狀況下不會經過在url中附加查詢字符串變量"_=[TIMESTAMP]"進行自動緩存結果,除非設置了cahce參數爲true。

4,在運程請求時(不在同一個域下),全部POST請求都將轉爲GET請求(由於將使用DOM的script標籤來加載)

inspectPrefiltersOrTransports(prefilters,s,options,jqXHR)
此時的dataType類型就會通過對應的預處理ajaxPrefilter("script"),其中s.cache(默認爲true,dataType爲script和jsonp時默認爲false)
jQuery.ajaxPrefilter("script",function(s){
  if(s.cache === undefined){
    s.cache = false;
  }
  if(s.srossDomain){
    s.type = "GET";
  }
});
預處理的處理就是將其緩存設置爲false,瀏覽器將不緩存此頁面,這將在請求的url的查詢字符串中追加一個時間戳參數,以確保每次瀏覽器下載的腳本被從新請求,工做原理是在GET請求參數中附加"_={timestamp}"在GET請求的地址後加一個時間戳。

json與jsonp

json:把響應的結果當作json執行,並返回一個JavaScript對象,若是指定的是json,響應結果做爲一個對象,在傳遞給成功處理函數以前使用jQuery.parseJSON進行解析,解析後的JSON對象能夠經過該jqXHR對象的responseJSON屬性得到的,json的處理只要是在ajaxConvert方法中把結果給轉換成須要是json格式,這是後面的內容,這裏主要研究jsonp的預處理。

JSONP:是一個非官方的協議,它容許在服務器端集成Script tags返回到客戶端,經過JavaScript callback的形式實現跨域訪問(這僅僅是JSONP簡單的實現形式),json系統開發方法是一種典型的面向數據結構的分析和設計方法,以活動爲中心, 一連串的活動的順序組合成一個完整的工做進程。

JSONP 出現的根源:

  • 跨域這個問題的產生根本緣由是瀏覽器的同源策略限制,理解同源策略的限制同源策略是指阻止代碼得到或者更改從另外一個域名下得到的文件或者信息。也就是說咱們的請求地址必須和當前網站的地址相同。同源策略經過隔離來實現對資源的保護,解決這個限制的一個相對簡單的辦法就是在服務器端發送請求,服務器充當一個到達第三方資源的代理中繼。雖然是使用普遍可是這個方法卻不夠靈活。
  • 另外一個辦法就是使用框架(frames),將第三方站點的資源包含進來,可是包含進來的資源一樣要受到同源策略的限制。
  • 有一個很巧妙的辦法就是在頁面中使用動態代碼元素,代碼的源指向服務地址並在本身的代碼中加載數據。當這些代碼加載執行的時候,同源策略就不會起到限制。可是若是代碼試圖下載文件的時候執行仍是會失敗,幸運的是,咱們可使用JSON(JavaScript Object Notation)來改進這個應用。

JSON和JSONP

與XML相比,JSON是一個輕量的數據交換格式,JSON對象對於JavaScript開發人員充滿魅力的緣由在於JSON自己就是JavaScript中的對象。

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)
}
  • 代碼經過動態加入 Javascript 代碼,來執行函數加載數據。正如以前提到過的,同源策略對於動態插入的代碼不適用。也就是你能夠從不一樣的域中加載代碼,來執行在他們代碼中的 JSON 數據。這就是 JSONP(JSON with Padding)。注意,使用這種方法時,你必須在頁面中定義回調函數,就像上例中的 showPrice 同樣。
  • 咱們一般所說的 JSONP 服務(遠程 JSON 服務),實際上就是一種擴展的支持在用戶定義函數中包含返回數據的能力。這種方法依賴於必須接受一個回調函數的名字做爲參數。而後執行這個函數,處理 JSON 數據,並顯示在客戶頁面上。

因此總結其實json的一個核心點:容許用戶傳遞一個callback參數給服務器端,而後服務器返回數據時會將這個callback參數做爲函數名來包裹住json數據,這樣客戶端就能夠隨意定製本身的函數來自動處理返回數據了。

jsonp的原理:

ajax和jsonp的區別:

ajax的核心是經過XmlHttpRequest獲取飛本頁面內容,jsonp的核心是動態添加script標籤來調用服務器提供的js,容許用戶傳遞一個callback參數給服務器端,而後服務器端返回數據時會將這個callback參數 做爲函數名包裹住json數據,這個客戶端就能夠隨意定製指定的本身的函數來自動處理返回數據了。

$.ajax({
        crossDomain :true,
        url: 'http://192.168.1.113:8080/github/jQuery/jsonp.php', //不一樣的域
        type: 'GET', // jsonp模式只有GET是合法的
        data: {
            'action': 'aaron'
        }, // 預傳參的數組
        dataType: 'jsonp', // 數據類型
        jsonp: 'callback', // 指定回調函數名,與服務器端接收的一致,並回傳回來
        jsonpCallback:"flightHandler",
        success: function(json) {
            console.log(json);
        }
    })function flightHandler(data){
        console.log(data)
    }

    function createJsonp(url, complete) {
        var script = jQuery("<script>").prop({
            async: true,
            src: "http://192.168.1.113:8080/github/jQuery/jsonp.php?callback=flightHandler&amp;action=aaron&amp;_=1418782732584"
        }).on(
            "load error",
            callback = function(evt) {
                script.remove();
                callback = null;
            }
        );
        document.head.appendChild(script[0]);
    }
    
    createJsonp()

jquery、ext、dojo 這類庫的實現手段其實大同小異,在同源策略下,在某個服務器下的頁面是沒法獲取到該服務器之外的數據的,但 img、iframe、script 等標籤是個例外,這些標籤能夠經過 src 屬性請求到其餘服務器上的數據。利用 script 標籤的開放策略,咱們能夠實現跨域請求數據,固然,也須要服務端的配合。通常的 ajax 是不能跨域請求的,所以須要使用一種特別的方式來實現跨域,其中的原理是利用 <script> 元素的這個開放策略。

這裏有2個重要的參數:

jsonpCallback:
爲 jsonp 請求指定一個回調函數名。這個值將用來取代 jQuery 自動生成的隨機函數名。這主要用來讓 jQuery 生成一個獨特的函數名,這樣管理請求更容易,也能方便地提供回調函數和錯誤處理。你也能夠在想讓瀏覽器緩存 GET 請求的時候,指定這個回調函數名。從jQuery 1.5 開始,你也可使用一個函數做爲該參數設置,在這種狀況下,該函數的返回值就是 jsonpCallback 的結果。

jsonp:
在一個 jsonp 請求中重寫回調函數的名字。這個值用來替代在 "callback=?" 這種 GET 或 POST 請求中 URL 參數裏的 "callback" 部分,好比 {jsonp:'onJsonPLoad'} 會致使將 "onJsonPLoad=?" 傳給服務器。在 jQuery 1.5,設置 jsonp 選項爲 false,阻止了 jQuery 從加入 "?callback" 字符串的 URL 或試圖使用 "=?" 轉換。在這種狀況下,你也應該明確設置 jsonpCallback 設置。例如, { jsonp: false, jsonpCallback: "callbackName" }。

當咱們正常地請求一個 JSON 數據的時候,服務端返回的是一串 JSON 類型的數據,而咱們使用 JSONP 模式來請求數據的時候,服務端返回的是一段可執行的 JavaScript 代碼,因此咱們可見服務器代碼最後一行。

$_GET['callback']).'('. json_encode(array('status'=>1,'info'=>'OK')) .')

就是執行的 backfunc 方法,而後把數據經過回調的方式傳遞過。

OK,就是整個流程就是:

客戶端發送一個請求,規定一個可執行的函數名(這裏就是 jQuery 作了封裝的處理,自動幫你生成回調函數並把數據取出來供 success 屬性方法來調用,不是傳遞的一個回調句柄),服務端接受了這個 backfunc 函數名,而後把數據經過實參的形式發送出去

 

jsonp的實現

咱們發送一個 jsonp 的請求:

$.ajax({
    crossDomain:true,//強制跨域
    url: ' http://url...’, //不一樣的域
    type: 'GET', // jsonp模式只有GET是合法的
    data: {
        'action': 'aaron'
    }, // 預傳參的數組
    dataType: 'jsonp', // 數據類型
    jsonp: 'backfunc', // 指定回調函數名,與服務器端接收的一致,並回傳回來
})

經過 ajax 請求不一樣域的實現,jsonp 底層不是靠 XmlHttpRequest 而是 script,因此不要被這個方法給迷惑了。

這裏有幾個要注意的:

  1. 在 ajax 請求中類型若是是 type 是 post,其實內部都只會用 get,由於其跨域的原理就是用的動態加載 script 的 src,因此咱們只能把參數經過 url 的方式傳遞
  2. 咱們使用了 dataType 是 'jsonp' 可是 jquery 內部有進一步的優化,若是探測到仍是同域下的請求,依然仍是用 XmlHttpRequest 處理,因此咱們在同域下測試的話,能夠把 crossDomain 選項置爲 true,這樣強制爲跨域處理,這樣就會經過 script 處理了,那麼根據 jsonp 的原理其實 jquery 內部會把 URL 最終會轉化成:
http://192.168.1.113:8080/github/jQuery/jsonp.php?callback=flightHandler&amp;action=aaron&amp;_=1418782732584 ">

而後經過建立腳本動態加載:

<script type="text/javascript" src=" http://192.168.1.113:8080/github/jQuery/jsonp.php?callback=flightHandler&amp;action=aaron&amp;_=1418782732584 "></script>

而後 php 方就會收到 get 請求的參數,經過解析出 callback 執行 callback 這個回調並傳遞參數。

要處理的幾個問題

1. 採用的是腳本請求的方法,因此雖然 dataType 是 'jsonp' 可是內部仍是按照 script 處理
2. get 請求的後綴拼接,編碼的處理
3. 避免緩存的處理

因此流程就會分二步:

  1. 針對 jsonp 的預處理,主要是轉化拼接這些參數,而後處理緩存,由於 jsonp 的方式也是靠加載 script 因此要關閉瀏覽器緩存
  2. inspectPrefiltersOrTransports中jsonp 的預處理後,還要在執行 inspect(dataTypeOrTransport); 的遞歸,就是爲了關閉這個緩存機制
  3. jquery 經過預處理會在 window 對象中加載一個全局的函數,當代碼插入時函數執行,執行完畢後就會被移除。同時 jquery 還對非跨域的請求進行了優化,若是這個請求是在同一個域名下那麼他就會像正常的 Ajax 請求同樣工做。


分發器執行代碼

當咱們全部的參數都轉化好了,此時會通過請求發送器用來處理髮送的具體,爲何會叫作分發器,由於發送的請求目標,ajax 由於參雜了 jsonp 的處理,因此實際上的請求不是經過 xhr.send(XmlHttpRequest) 發送的,而是經過 get 方式的腳本加載的,因此 transports 對象在初始化構件的時候,會生成 2 個處理器

*: Array[1]     針對xhr方式
script: Array[1]  針對script,jsonp方式

因此 transport = inspectPrefiltersOrTransports(transports, s, options, jqXHR),那麼獲得的 transport 就會根據當前的處理的類型,來選擇採用哪一種發送器(*、script)因此最終的實現就是經過動態加載腳本!

什麼是類型轉化器?

jQuery 支持不一樣格式的數據返回形式,好比 dataType 爲 xml、json、jsonp、script、html。可是瀏覽器的 XMLHttpRequest 對象對數據的響應只有 responseText 與 responseXML 這2種,因此如今我要定義 dataType 爲 jsonp,那麼所得的最終數據是一個 json 的鍵值對,因此 jQuery 內部就會默認幫你完成這個轉化工做,jQuery 爲了處理這種執行後數據的轉化,就引入了類型轉化器,若是沒有指定類型就依據響應頭 Content-Type 自動處理數據傳輸,服務器只能返回字符串形式的,因此若是咱們 dataType 爲 jsop 或者 json 的時候服務器返回的數據爲:

responseText: "{"a":1,"b":2,"c":3,"d":4,"e":5}"

給轉化成:

responseJSON: Object
    {
        a: 1
        b: 2
        c: 3
        d: 4
        e: 5
    }

服務器的傳輸返回的只能是 string 類型的數據,可是用戶若是經過 jQuery 的 dataType 定義了 json 的格式後,會默認把數據轉換成 Object 的形式返回,這就是 jQuery 內部作的智能處理了,jQuery 內把自定義的 dataType 與服務器返回的數據作相對應的映射處理,經過 converters 存儲對應的處理句柄,把須要類型轉換器 ajaxConvert 在服務端響應成功後,對定義在 jQuery. ajaxSettings 中的 converters 進行遍歷,找到與數據類型相匹配的轉換函數,並執行。

converters的映射

converters: {
    // Convert anything to text、
    // 任意內容轉換爲字符串
    // window.String 將會在min文件中被壓縮爲 a.String
    "* text": window.String,
    // Text to html (true = no transformation)
    // 文本轉換爲HTML(true表示不須要轉換,直接返回)
    "text html": true,
    // Evaluate text as a json expression
    // 文本轉換爲JSON
    "text json": jQuery.parseJSON,
    // Parse text as xml
    // 文本轉換爲XML
    "text xml": jQuery.parseXML
}


除此以外還有額外擴展的一部分 jsonp 的處理,因此其格式就是。

text –> (html,json,script)的處理了

其寓意就是服務器返回的用於只是 string 類型的文本格式,須要轉化成用戶想要的 dataType 類型的數據:

{"* text": window.String, "text html": true, "text json": jQuery.parseJSON, "text xml": jQuery.parseXML}

類型的轉化都是發生在服務器返回數據後,因此對應的就是 ajax 方法中的 done 以後,固然這個 done 方法也是通過請求分發器包裝過的,至於爲何要這樣處理,上章就已經提到過了,爲了處理正常請求與 jsonp 的跨域請求的問題。

因此當 AJAX 請求完成後,會調用閉包函數 done,在 done 中判斷本次請求是否成功,若是成功就調用 ajaxConvert 對響應的數據進行類型轉換。

因此在此以前須要:

1. 正確分配 dataType 類型,若是用戶不設置(空)的狀況
2. 須要轉化成 converters 映射表對應的格式好比(* text, text html , text xml , text json)

類型的適配

dataType 類型的轉化

dataType 類型的參數,能夠是 xml, json, script, or html 或者乾脆爲空,那麼 jQuery 就須要一個方法去判斷當前是屬於什麼數據處理,就此引入了 ajaxConvert 處理響應轉化器,解析出正確的 dataType 類。

response = ajaxConvert(s, response, jqXHR, isSuccess);

分析下 dataType 沒法就那麼幾種狀況

1. dataType 爲空,自動轉化

此時 jQuery 只能根據頭部信息來猜想當前須要處理的類型,刪除掉通配 dataType,獲得返回的 Content-Type。

while (dataTypes[0] === "*") {
    dataTypes.shift();
    if (ct === undefined) {
        ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
    }
}

經過 xhr.getAllResponseHeaders() 獲得頭部信息,而後去匹配 Content-Type 全部對象的值便可,固然找到這個 Content-Type = 「html」,咱們還得看看有沒有對應處理的方法,若是有就須要替換這個 dataTypes。

看看是否是咱們能處理的 Content-Type,好比圖片這類二進制類型就很差處理了。

if (ct) {
    // 實際上能處理的就是text、xml和json
    for (type in contents) {
        if (contents[type] && contents[type].test(ct)) {
            dataTypes.unshift(type);
            break;
        }
    }
}

通過這個流程後,dataTypes 原本是 * 就變成了對應的 html了,這是 jquery 內部的自動轉化過。


2. dataType開發者指定

xml, json, script, html, jsop類型轉換器將服務端響應的 responseText 或 responseXML,轉換爲請求時指定的數據類型 dataType,若是沒有指定類型就依據響應頭 Content-Type 自動處理。

類型轉換器的執行過程

response = ajaxConvert(s, response, jqXHR, isSuccess);

流程

1.遍歷dataTypes中對應的處理規則【"script","json"】
2.製做jqXHR對象的返回數據接口
    json: "responseJSON"
    text: "responseText"
    xml: "responseXML"
    如:jqXHR.responseText: "{"a":1,"b":2,"c":3,"d":4,"e":5}"
3.生成轉化器對應的匹配規則,尋找合適的處理器
4.返回處理後的數據response

分析一下特殊的 jsonp 的轉化流程,先看看轉化對應的處理器。

jsonp

converters["script json"] = function() {
    if (!responseContainer) {
      jQuery.error(callbackName + " was not called");
    }
   return responseContainer[0];
};

jsonp 的轉化器只是很簡單的從 responseContainer 取出了對應的值,因此 responseContainer 確定在轉化以後就應該把數據給轉化成數組對象了,固然作源碼分析須要一點本身猜測能力,好比 responseContainer 這個數組對象如何而來?

那麼咱們知道 jsonp 的處理的原理,仍是經過加載 script,而後服務器返回一個回調函數,responseContainer 數據就是回調函數的實參,因此須要知足 responseContainer 的處理,必需要先知足腳本先加載,因此咱們要去分發器中找對應的加載代碼,首先responseContainer 是內部變量,只有一個來源處,在預處理的時候增長一個全局的臨時函數,而後代碼確定是執行了這個函數才能把 arguments 參數賦給 responseContainer。

overwritten = window[callbackName];
window[callbackName] = function() {
    responseContainer = arguments;
};
//callbcakName是內部建立的一個尼瑪函數名
jQuery203029543792246840894_1403062512436 = function() {
    responseContainer = arguments;
};

咱們發送請求:

http://192.168.1.114/yii/demos/test.php?backfunc=jQuery203029543792246840894_1403062512436&action=aaron&_=1403062601515

服務器那邊就回調後,執行了 jQuery203029543792246840894_1403062512436(responseContainer ) 因此全局的 callbackName 函數須要在分發器中腳本加載後才能執行,從而才能截取到服務器返回的數據。

相關文章
相關標籤/搜索