深入淺出 JSONP

前言

第一次聽說jsonp,其實早在2年之前。當時在做一個活動頁面的抽獎模塊,要從服務端get一個概率,當時什麼都不懂,同事說用ajax,我就用ajax,同事說dataType改成jsonp,我就改成jsonp。於是乎活動頁面做完了,以後也沒有碰到過jsonp,在這期間我一直以爲jsonp跟ajax息息相關,是xhr的一種特殊的跨域形式…直到一個月前的一次面試,問到jsonp我被虐成狗,才決定看下jsonp,好吧,原來jsonp也不是很難。

爲什麼要用jsonp?

相信大家對跨域一定不陌生,對同源策略也同樣熟悉。什麼,你沒聽過?沒關係,既然是深入淺出,那就從頭說起。

假如我寫了個index頁面,頁面裏有個請求,請求的是一個json數據(不知道json數據的猛戳JSON簡介以及用法彙總),簡單思考寫下如下代碼:

JavaScript

 

1

2

3

4

5

6

7

8

9

10

<script src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>

<script type="text/javascript">

  $.ajax({

    url: 'http://localhost/a.json',

    dataType: "json",

    success: function (data) {

      console.log(data);

    }

  })

</script>

 

JavaScript

 

1

2

3

4

{

  "name": "hanzichi",

  "age": 10

}

樓主把兩個文件都放在wamp下的www文件夾下,ajax請求沒有跨域,完美得到結果:

但是如果我的json文件和index文件不在一個域下,即跨域(不懂跨域的可參考JavaScript 的同源策略)了呢?

試着在wamp下新開個apache端口(不知道怎麼開的可參考WampServer下使用多端口訪問),將json文件放到該服務端口的文件夾下(樓主設置的端口號爲8080,默認的是80端口),試着發送請求:

JavaScript

 

1

2

3

4

5

6

7

8

9

10

<script src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>

<script type="text/javascript">

  $.ajax({

    url: 'http://localhost:8080/a.json',

    dataType: "json",

    success: function (data) {

      console.log(data);

    }

  })

</script>

很顯然,提示跨域了!怎麼搞?這時jsonp就要出馬了!

神奇的script標籤

與jsonp息息相關的是script標籤,而xhr或者說傳統意義上的ajax與之沒有半毛錢關係!

接着看上面的index.html代碼,我們看到頁面引用了百度cdn的jquery路徑,對於這樣的方式我們似乎已經習以爲常,但是仔細一想,script標籤可是完完全全的跨域的啊…沒錯,jsonp的實現核心就是利用script標籤的跨域能力!於是我們靈機一動,似乎可以這麼搞,動態生成一個script標籤,把json的url賦值給script的src屬性,然後再把這個script標籤插入dom裏…

JavaScript

 

1

2

3

4

5

6

7

8

<body>

  <script src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>

  <script type="text/javascript">

    var s = document.createElement('script');

    s.src = 'http://localhost:8080/a.json';

    document.body.appendChild(s);

  </script>

</body>

我們創建了一個script標籤,而標籤內包裹的內容正是需要的json數據,但是報錯如下:

原因是因爲json數據並不是合法的js語句,把上面的json數據放在一個回調函數中是最簡單的方法:

JavaScript

 

1

2

3

4

5

6

7

8

9

10

11

12

<body>

  <script src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>

  <script type="text/javascript">

    function jsonpcallback(json) {

      console.log(json);

    }

 

    var s = document.createElement('script');

    s.src = 'http://localhost:8080/a.json';

    document.body.appendChild(s);

  </script>

</body>

 

JavaScript

 

1

2

3

4

jsonpcallback({

  "name": "hanzichi",

  "age": 10

});

當然,這時的a.json文件並不一定要這樣命名,改成a.js也不會有一點問題。

而如果是與服務端交互也是一樣的道理,比如和php:

JavaScript

 

1

2

3

4

5

6

7

8

9

10

11

12

<body>

  <script src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>

  <script type="text/javascript">

    function jsonpcallback(json) {

      console.log(json);

    }

 

    var s = document.createElement('script');

    s.src="http://localhost:8080/test.php?callback=jsonpcallback";

    document.body.appendChild(s);

  </script>

</body>

 

JavaScript

 

1

2

3

4

5

6

7

<?php

  $jsondata = '{

    "name": "hanzichi",

    "age": 10

  }';

  echo $_GET['callback'].'('.$jsondata.')';

?>

需要注意的是,jsonp提供的url(即動態生成的script標籤的src),無論看上去是什麼形式,最終生成返回的都是一段js代碼。

JQuery對jsonp的封裝

爲了便於開發,jq對jsonp也進行了封裝,封裝在了ajax方法中。

JavaScript

 

1

2

3

4

5

6

7

8

9

10

11

<script src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>

<script type="text/javascript">

  $.ajax({

    url: 'http://localhost:8080/a.json',

    dataType: 'jsonp',

    jsonpCallback: 'CallBack',

    success: function (data) {

      console.log(data);

    }

  });

</script>

 

JavaScript

 

1

2

3

4

CallBack({

  "name": "hanzichi",

  "age": 10

});

以上代碼是針對請求文件中寫死了callback函數名的情況。因爲請求的是json文件,json不是服務器端的動態語言不能進行解析,如果是php或者其他的服務器端語言,則不用寫死函數名,比如下面這樣:

JavaScript

 

1

2

3

4

5

6

7

8

9

10

<script src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>

<script type="text/javascript">

  $.ajax({

    url: 'http://localhost:8080/test.php',

    dataType: 'jsonp',

    success: function (data) {

      console.log(data);

    }

  });

</script>

 

JavaScript

 

1

2

3

4

5

6

7

<?php

  $jsondata = '{

    "name": "hanzichi",

    "age": 10

  }';

  echo $_GET['callback'].'('.$jsondata.')';

?>

當然類似的封裝好的方法還有幾種:

JavaScript

 

1

2

3

4

5

6

7

8

9

// 1

$.getJSON("http://localhost:8080/test.php?callback=?", function(data) {  

  console.log(data);

});

 

// 2

$.get('http://localhost:8080/test.php', function(data) {  

  console.log(data);

}, 'jsonp');

需要注意的是getJSON方法的請求地址url需要帶上callback=?,因爲jq對該方法進行封裝的時候並沒有默認回調函數變量名爲callback,於是php中$_GET[‘callback’]就找不到變量值了。

而一般的jq方法url 中不用指定 callback 參數。對於 jQuery 中的 jsonp 來說,callback 參數是自動添加的。默認情況下,jQuery 生成的 jsonp 請求中 callback 參數是形如 callback=jQuery200023559735575690866_1434954892929 這種根據看似隨機的名字,對應的就是 success 那個處理函數,所以一般不用特意處理。二如果要寫死callback名的話,可以參照上文。

總結

由於同源策略的限制,XmlHttpRequest只允許請求當前源(域名、協議、端口)的資源,爲了實現跨域請求,可以通過script標籤實現跨域請求,然後在服務端輸出JSON數據並執行回調函數,從而解決了跨域的數據請求,這就是jsonp的核心。

jsonp原理:

  1. 首先在客戶端註冊一個callback, 然後把callback的名字傳給服務器。
  2. 服務器先生成 json 數據。 然後以 javascript 語法的方式,生成一個function , function 名字就是傳遞上來的參數 jsonp. 最後將 json 數據直接以入參的方式,放置到 function 中,這樣就生成了一段 js 語法的文檔,返回給客戶端。
  3. 客戶端瀏覽器,解析script標籤,並執行返回的 javascript 文檔,此時數據作爲參數,傳入到了客戶端預先定義好的 callback 函數裏.(動態執行回調函數)

 

原文出處: 韓子遲