1. 程式人生 > >JSONP跨域詳解

JSONP跨域詳解

JSON和JSONP雖然只有一個字母的差別,但其實他們根本不是一回事兒:JSON是一種資料交換格式,而JSONP是一種依靠開發人員的聰明才智創造出的一種非官方跨域資料互動協議。可見一個是描述資訊的格式,一個是資訊傳遞雙方約定的方法。
1、什麼是JSON
2、什麼是JSONP?
2.1、先說下JSONP是怎麼產生的
1)一個眾所周知的問題,AJAX直接請求普通檔案存在跨域無許可權訪問的問題,甭管你是靜態頁面、動態網頁、web服務、WCF,只要是跨域請求,一律不準;

2)不過我們又發現,Web頁面上呼叫js檔案時則不受是否跨域的影響(不僅如此,我們還發現凡是擁有src這個屬性的標籤都擁有跨域的能力,比如<script>

<img><iframe>);

3)為了便於客戶端使用資料,逐漸形成了一種非正式傳輸協議,人們把它稱作JSONP,該協議的一個要點就是允許使用者傳遞一個callback引數給服務端,然後服務端返回資料時會將這個callback引數作為函式名來包裹住JSON資料,這樣客戶端就可以隨意定製自己的函式來自動處理返回資料了。
2.2、JSONP的客戶端具體實現
1)我們知道,哪怕跨域js檔案中的程式碼(當然指符合web指令碼安全策略的),web頁面也是可以無條件執行的。
遠端伺服器remoteserver.com根目錄下有個remote.js檔案程式碼如下:

alert('我是遠端檔案');

本地伺服器localserver.com下有個jsonp.html頁面程式碼如下:

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
  </head>
  <body></body>
</html>

2)現在我們在jsonp.html頁面定義一個函式,然後在遠端remote.js

中傳入資料進行呼叫。
jsonp.html頁面程式碼如下:

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <script type="text/javascript">
      var localHandler = function(data){
          alert('我是本地函式,可以被跨域的remote.js檔案呼叫,遠端js帶來的資料是:' + data.result);
      };
    </script>
    <script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
  </head>
  <body></body>
</html>

remote.js檔案程式碼如下:

localHandler({
  "result":"我是遠端js帶來的資料"
});

雖然跨域請求成功,當時怎麼讓遠端js知道它應該呼叫的本地函式叫什麼名字呢?
3)只要服務端提供的js指令碼是動態生成的,這樣呼叫這可以傳一個引數過去告訴服務端“我想要一段呼叫XXX函式的js程式碼,請你返回給我”,於是伺服器就可以按照客戶端的需求來生成js指令碼並相應了。
jsonp.html頁面的程式碼:

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <script type="text/javascript">
      // 得到航班資訊查詢結果後的回撥函式
      var flightHandler = function(data){
          alert('你查詢的航班結果是:票價 ' + data.price + ' 元,' + '餘票 ' + data.tickets + ' 張。');
      };
      // 提供jsonp服務的url地址(不管是什麼型別的地址,最終生成的返回值都是一段javascript程式碼)
      var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler";
      // 建立script標籤,設定其屬性
      var script = document.createElement('script');
      script.setAttribute('src', url);
      // 把script標籤加入head,此時呼叫開始
      document.getElementsByTagName('head')[0].appendChild(script); 
    </script>
  </head>
  <body></body>
</html>

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

我們看到呼叫的url中傳遞了一個code引數,告訴伺服器我要查的是CA1998次航班的資訊,而callback引數則告訴伺服器,我的本地回撥函式叫做flightHandler,所以請把查詢結果傳入這個函式中進行呼叫。

這個叫做flightResult.aspx的頁面生成了一段這樣的程式碼提供給jsonp.html(服務端的實現這裡就不演示了,與你選用的語言無關,說到底就是拼接字串):

flightHandler({
  "code": "CA1998",
  "price": 1780,
  "tickets": 5
});

我們看到,傳遞給flightHandler函式的是一個json,它描述了航班的基本資訊。

2.3 總結原生JS實現JSONP的步驟
2.3.1 客戶端
定義獲取資料後呼叫的回撥函式
動態生成對服務端JS進行引用的程式碼
設定url為提供jsonp服務的url地址,並在該url中設定相關callback引數
建立script標籤,並設定其src屬性
把script標籤加入head,此時呼叫開始。
2.3.2 服務端
將客戶端傳送的callback引數作為函式名來包裹住JSON資料,返回資料至客戶端。
2.4 JSONP在jQuery中的具體實現
在jQuery中實現JSONP主要有兩種方式。

$.getJSON
$.ajax
2.4.1 $.getJSON實現方式

<script type="text/javascript" src="jquery.js"></script>  
<script type="text/javascript">  
    $.getJSON("http://crossdomain.com/services.php?callback=?", function(json){
        alert('您查詢到航班資訊:票價: ' + json.price + ' 元,餘票: ' + json.tickets + ' 張。');
    });  
</script>

2.4.2 $.ajax實現方式

<script type="text/javascript" src=jquery.min.js"></script>
<script type="text/javascript">
     $(document).ready(function(){ 
        $.ajax({
             type: "get",
             url: "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998",
             dataType: "jsonp",
             jsonp: "callback",//傳遞給請求處理程式或頁面的,用以獲得jsonp回撥函式名的引數名(一般預設為:callback)
             jsonpCallback:"flightHandler",//自定義的jsonp回撥函式名稱,預設為jQuery自動生成的隨機函式名,也可以寫"?",jQuery會自動為你處理資料
             success: function(json){
                 alert('您查詢到航班資訊:票價: ' + json.price + ' 元,餘票: ' + json.tickets + ' 張。');
             },
             error: function(){
                 alert('fail');
             }
         });
     });
</script>

為什麼我這次沒有寫flightHandler這個函式呢?而且竟然也執行成功了!哈哈,這就是jQuery的功勞了,jquery在處理JSONP型別的ajax時(還是忍不住吐槽,雖然jQuery也把jsonp歸入了ajax,但其實它們真的不是一回事兒),自動幫你生成回撥函式並把資料取出來供success屬性方法來呼叫。
3. JSONP與GET / POST 請求
我們知道script,link,img 等標籤引入外部資源,都是GET請求的,那麼就決定了 JSONP 一定是GET的。即便在$.ajax()中使用POST請求也能成功。一旦當我們指定dataType: 'jsonp',不管指定type的值是什麼(GET/POST/甚至不寫),都會進行GET請求。
這是jQuery在封裝JSONP跨域時就已經寫死了的。
對應的原始碼如下:

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

if( s.crossDomain){ s.type = "GET"; ...}這裡就是真相~ 在ajax的過濾函式中,只要是跨域,jQuery就將其type設定成GET,真是那句話:在原始碼面前,一切了無祕密~ jQuery原始碼我自己很多地方讀不懂,但是並不妨礙我們去讀,去探索~
4. AJAX與JSONP的異同:
1)AJAX和JSONP這兩種技術在呼叫方式上“看起來”很像,目的也一樣,都是請求一個url,然後把伺服器返回的資料進行處理,因此jQuery和extjs等框架都把JSONP作為AJAX的一種形式進行了封裝;

2)但AJAX和JSONP其實本質上是不同的東西。AJAX的核心是通過XmlHttpRequest獲取非本頁內容,而JSONP的核心則是動態新增<script>標籤來呼叫伺服器提供的js指令碼。

3)所以說,其實AJAX與JSONP的區別不在於是否跨域,AJAX通過服務端代理一樣可以實現跨域,JSONP本身也不排斥同域的資料的獲取。

4)還有就是,JSONP是一種方式或者說非強制性協議,如同AJAX一樣,它也不一定非要用JSON格式來傳遞資料,如果你願意,字串都行,只不過這樣不利於用JSONP提供公開服務。

5)總而言之,JSONP不是AJAX的一個特例,哪怕jQuery等巨頭把它封裝進了AJAX,也不能改變這一點!

參考:公子七-JSONP跨域詳解