一篇文章吃透Jsonp

前言

最近由於工做的緣故,幾乎把市面上全部Jsonp庫都下載了一遍,卻發現沒有百分百讓我滿意的,最後本身手動改寫了Jsonp,才符合了要求,也所以有了這篇文章。本文示例詳實,代碼簡單,想弄明白Jsonp, 這一篇文章就夠了。javascript

什麼是Jsonp

由於AJAX收到瀏覽器同源策略的限制,致使在跨域上有心無力,常常須要後臺同窗的幫助。而在瀏覽器中,全部帶有src的標籤都是不受同源策略限制的,如image, script。Jsonp上就是利用了script標籤的這個特色,來實現跨域的。html

其中,Jsonp和AJAX的原理徹底不一樣,只不過Jquery帶了個很很差的頭,把兩個東西封裝在一塊兒了,因此常常讓新的同窗混淆了。前端

Jsonp的原理:script src
AJAX的原理:xhr

舉一個最簡單的Jsonp的例子:java

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Jsonp簡單示例</title>
</head>

<body>
</body>
<script type="text/javascript">
    // Jsonp回調
    function jsonCallback(data) {
        console.log(data);
    }

    //添加<script>標籤的方法
    function addScriptTag(src) {
        var script = document.createElement('script');
        script.setAttribute("type", "text/javascript");
        script.src = src;
        document.body.appendChild(script);
    }

    // 向後臺發起請求(連接是胡亂寫的)
    addScriptTag('http://www.qq.com/getJsonp?callback=jsonCallback')
</script>

</html>

http://www.qq.com/getJsonp?callback=jsonCallback這個連接返回的內容應該是node

jsonpCallback({
    msg: success
})

這樣,至關於後臺調用了前臺提早寫好的callback函數,將要返回的數據當作callback函數的參數傳入,這樣前端就拿到後臺傳回來的數據了。webpack

改寫Jsonp

可是坦白來講,單純的拿到數據並不能讓咱們滿意。一個合適的請求函數,必然包含對成功、失敗、超時的處理,就像咱們上面寫的那個簡單示例,一旦出現異常,就不能讓咱們滿意了。ios

在這一點上不得不說Jquery作的很好,Jquery的Jsonp函數包含了對各類狀況的處理,還僞造了一個http狀態碼的返回。es6

Jsonp和AJAX不一樣,是拿不到狀態碼的,可是Jquery對於全部的錯誤都賦予了一個404的狀態碼,也是機智

對比其餘的組件庫(axios-jsonp, axios-jsonp-pro, jsonp, fetch=jsonp-es6), 要不就是徹底沒有對超時的處理,要否則就是把錯誤和超時混成一譚,更有甚者,有些都不能自定義callback函數的名字。這簡直太過度了。web

那我爲何不選擇Jquery呢?由於太大了,webpack引入JQuery後瞬間大了80K, 並且單獨將Jsonp打包出來也有70K的樣子,而個人源碼只有20K,這是我不能接受的。npm

jsonp這個組件的問題是沒有對錯誤的處理,理解了Jsonp的原理,咱們能很容易的添加上這塊的邏輯,如下是添加後的源碼:

/**
 * Module exports.
 */

module.exports = jsonp;

/**
 * Callback index.
 */

var count = 0;

/**
 * Noop function.
 */

function noop(){}

/**
 * JSONP handler
 *
 * Options:
 *  - param {String} qs parameter (`callback`)
 *  - prefix {String} qs parameter (`__jp`)
 *  - name {String} qs parameter (`prefix` + incr)
 *  - timeout {Number} how long after a timeout error is emitted (`60000`)
 *
 * @param {String} url
 * @param {Object|Function} optional options / callback
 * @param {Function} optional callback
 */

function jsonp(url, opts, fn){
  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }
  if (!opts) opts = {};

  var prefix = opts.prefix || '__jp';

  // use the callback name that was passed if one was provided.
  // otherwise generate a unique name by incrementing our counter.
  var id = opts.name || (prefix + (count++));

  var param = opts.param || 'callback';
  var timeout = null != opts.timeout ? opts.timeout : 60000;
  var enc = encodeURIComponent;
  var target = document.getElementsByTagName('script')[0] || document.head;
  var script;
  var timer;


  if (timeout) {
    timer = setTimeout(function(){
      cleanup();
      if (fn) fn(new Error('Timeout'));
    }, timeout);
  }

  function cleanup(){
    if (script.parentNode) script.parentNode.removeChild(script);
    window[id] = noop;
    if (timer) clearTimeout(timer);
  }

  function cancel(){
    if (window[id]) {
      cleanup();
    }
  }

  window[id] = function(data){
    cleanup();
    if (fn) fn(null, data);
  };

  // add qs component
  url += (~url.indexOf('?') ? '&' : '?') + param + '=' + enc(id);
  url = url.replace('?&', '?');

  // create script
  script = document.createElement('script');
  script.src = url;
  
  // 添加對錯誤的處理
  script.onerror = function (evt) {
      if (fn) fn(new Error('Error'));
      if (timer) clearTimeout(timer)
  }
  target.parentNode.insertBefore(script, target);

  return cancel;
}

由於大部分是人家的代碼,我也就不班門弄斧了,有須要的能夠直接npm install jsonp, 而後比對node_modules/jsonp/index.js進行修改;有須要對Jsonp有更詳細的處理的,也能夠在個人基礎上繼續添加。

總結

Jsonp的本質就是建立一個回調函數,而後在遠程服務上調用這個函數而且將JSON數據形式做爲參數傳遞,完成回調。比起另外兩種後臺無感知的跨域方案:image src、fetch no-cor,Jsonp能夠對錯誤和超時進行處理,也能對後臺返回的數據進行分析;而對於AJAX,Jsonp免去了後臺添加跨域頭的煩惱,後臺的改動較小,一次寫好,終生受用(跨域頭還要不斷維護白名單)。這三種方案都有各自的使用場景,要在不一樣的場景進行恰當的選用,以上。

相關文章
相關標籤/搜索