深入分析jsonp协议原理

今天在开发联调的过程中,需要跨域的获取数据,因为使用的jquery,当然使用dataType:'jsonp'就能够很easy的解决了。
但是因为当时后端没有支持jsonp来访问,后来他在实现这个功能的时候问了我一句,jsonp形式返回的格式是怎么样子的?我一直以来只知道怎么使用,迷迷糊糊的却没有答上来。。。

虽然后来解决了,但是对于喜欢解决问题的我,心里却一直耿耿于怀,必须得把这个研究透彻了,于是我开始翻阅资料,看到后面真有种豁然开朗的感觉,于是打算做个笔记与大家分享。

JSON和JSONP的区别

JSON和JSONP虽然只有一个字母的差别,但其实他们根本不是一回事儿:JSON是一种数据交换格式,而JSONP是一种跨域数据交互的协议,使用JSONP方法获取到的仍然是json格式的数据。

说白了,用JSON来传数据,靠JSONP来跨域

JSONP详细阐述

我们都知道,一个页面的ajax只能获取和此页面同域的数据。,所以当我们需要跨域获取数据的时候就需要使用到JSONP方法来获取了。

如下图所示,就是使用json格式获取跨域数据返回的错误提示:

那么该如何解决呢?使用框架的前端童鞋们可能都有自己相应的办法,比如jquery就是把dataType设为jsonp就能解决了,但是我们在使用的时候有没有想过,为什么这样就能解决呢?中心思想又是什么呢?

下面就开始为大家详细阐述,首要思想就是利用scirpt标签来引入跨域的数据。我们从最开始慢慢来深入jsonp的过程。

引导步骤1

编写b.com/b.js内容:

代码如下:

alert(‘hello');

然后编写a.com/a.html内容:

代码如下:

<script type='text/javascript' src='http://b.com/b.js'>

运行a.html,结果很明显,肯定会弹出hello。

引导步骤2

修改b.com/b.js文件内容:

代码如下:

myFunction('hello');

然后修改a.com/a.html内容:

<script type='text/javascript' src='http://b.com/b.js'>
<script>
function myFunction(str)
{ //定义处理数据的函数
alert(str + ' world');
}
</script>

运行a.html 结果是弹出‘hello world'。这个应该也毫无疑问。

引导步骤3

让我们再看一下上面的步骤2,b.js中的‘hello'就是b.com域名下的数据了,而能够在a.com/a.html中执行显示出来,这不就已经实现了跨域请求数据了吗?

另外,因为script标签中的src 不一定要指向js文件,而可以指向任何地址。

所以,我们把上面步骤2中a.html的内容:<script type='text/javascript' src='http://b.com/b.js'>,我们把其中的b.js改成b.html或者b.json等等都是可以的,执行都能正常返回。

引导步骤4

上面的数据都是静态的,是在文件内写死的,所以并不能满足我们的需求了吧。。。因为我们ajax请求数据是实时变化的,所以我们要把数据变成动态的了。

我们可以让script表器去调用一个动态的页面(接口),去实现获取动态数据,这里就想到了回调函数.

编辑a.com/a.html页面内容:

<script type='text/javascript' src='http://b.com/b.aspx?callback=myFunction'>
 <script>
   function myFunction(str){ //定义处理数据的函数
    alert(str + ' world');
   }
</script>

我们在src引用地址中加了?callback=myFunction,意思是把显示数据的函数也动态的传入了。

使用jsonp方法获取数据,还有一个要点就是后端接口也要支持jsonp才行,比如下面一段代码就是让返回的数据变成jsonp的格式,请继续看:(此处使用.net语言作为例子)

protected void page_load(object sender, EventArgs e){
  if(this.IsPostBack == false){
    string callback = '';
    if(Request["callback"] != null){
      callback = request["callback"];
      string data = "hello";
      Response.Write(callback+"("+ data + ")"); //接口页面返回的数据格式“函数(参数)”的格式。
    }
  }
}

代码的意思很简单,就是获取调用函数的参数。如果这里调用b.aspx?callback=myFunction的话,则会返回myFunction('hello'),如果后端代码给data赋值一个变量,这里的‘hello'则变成了动态的数据了。

引导步骤5

再看上面的步骤,虽然获取的数据是动态的了,但在页面上引入一个script标签,却只能执行一次,获取一次,显然还是不能满足需求的。所以我们在需要的时候,就得动态的添加一次这样的script标签。

所以我们在这里需要封装一个函数:

function addScript(src){
   var script = document.createElement('script');
   script.setAttribute('type','text/javascript');
   script.src= src;
   document.body.appendChild(script);
 }

需要调用的时候,就去执行:

addScript('b.com/b.aspx?callback=myFunction');

   function myFunction(data){//定义处理数据的函数
     alert(data);
   }

ok,上面的过程就是jsonp的原理,我们不必去记住那些令人纠结不清的定义,只要看一遍这个过程,我相信就能明白其中的精髓了吧。

jquery实现跨域

jquery跨域方法

$.ajax({
  url: 'b.com/b.json', //不同的域
  type: 'GET', // jsonp模式只有GET是合法的
  dataType: 'jsonp', // 数据类型
  jsonp: 'callback', // 指定回调函数名,与服务器端接收的一致,并回传回来
  success: function(data) {
    console.log(data);
  }
})

使用jquery非常方便,那么它是怎么实现这个转化的呢?下面我们来看看这部分的jquery源码。

jq实现jsonp源码分析

我贴出网上给的jquery实现jsonp部分的源码分析:

if (s.dataType == "jsonp") {   // 构建jsonp请求字符集串。jsonp是跨域请求,要加上callback=?后面将会加函数名             
  if (type == "GET") { //使get的url包含 callback=?后面将 会进行加函数名
    if (!s.url.match(jsre))      s.url += (s.url.match(/?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";     
  } // 构建新的s.data,使其包含 callback=function name
  else if (!s.data || !s.data.match(jsre))    s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
  s.dataType = "json";
}
//判断是否为jsonp,如果是 ,进行处理。
if (s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre))) {  
  jsonp = "jsonp" + jsc ++; //为请 求字符集串的callback=加上生成回调函数名
  if (s.data) s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");  
  s.url = s.url.replace(jsre, "=" + jsonp + "$1"); // 我们需要保证jsonp 类 型响应能正确地执行
     //jsonp的类型必须为script。这样才能执行服 务器返回的
     //代码。这里就是调用这个回调函数。
  s.dataType = "script";
  //window下注册一个jsonp回调函数 有,让ajax请求返回的代码调用执行它,
  window[jsonp] = function(tmp) {   
    data = tmp;
    success();
    complete();   // 垃圾回收,释放联变量,删除jsonp的对象,除去head中加的script元素  
    window[jsonp] = undefined;   
    try { 
      delete window[jsonp];     
    } catch (e) {}   
    if (head)  head.removeChild(script);   
  };  
}
if (s.data && type == "GET") {    // data有效,追加到get类型的url上去
  s.url += (s.url.match(/?/) ? "&" : "?") + s.data;    // 防止IE会重复发送get和post data
  s.data = null;
}
if (s.dataType == "script"  && type == "GET" && parts && (parts[1] && parts[1] != location.protocol || parts[2] != location.host)) {    // 在head中加上<script src=""></script>
  var head = document.getElementsByTagName("head")[0];   
  var script = document.createElement("script");   
  script.src = s.url;   
  if (s.scriptCharset) script.charset = s.scriptCharset;
  if (!jsonp) {  //如果datatype不是jsonp,但是url却是跨域 的。采用scriptr的onload或onreadystatechange事件来触发回 调函数。
    var done = false; // 对所有浏览器都加上处理器
    script.onload = script.onreadystatechange = function() {     
      if (!done && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {         
        done = true; 
        success();        
        complete();
        head.removeChild(script);       
      }   
    };  
  }  
  head.appendChild(script); // 已经使用 了script 元素注射来处理所有的事情
  return undefined;
}

上面的代码稍显复杂,但是我们挑拣重要的看就好了。

我们来分析一下这个过程,其实这个过程也就是上面我提出问题的答案了:

这里执行代码之后,其实就是判断是否配置了dataType: 'jsonp',如果是jsonp协议,则要在url上加callback=jQueryxxx(函数名),jquery会把url转化为:http://b.com/b.json?callback=jQueryxxx,然后再在html中插入,加载完b.json这个文件后,就会执行jQueryxxx这个回调函数,而且此时这个函数里面已经存在了动态数据(json格式数据),所以在页面上执行的时候就能够随心所欲的处理数据了,但是也别忘了后端也要支持jsonp格式才行。所以这样就达到了跨域获取数据的功能。

原生js封装jsonp

function jsonp(config) {
    var options = config || {};  // 需要配置url, success, time, fail四个属性
    var callbackName = ('jsonp_' + Math.random()).replace(".", "");
    var oHead = document.getElementsByTagName('head')[0];
    var oScript = document.createElement('script');
    oHead.appendChild(oScript);
    window[callbackName] = function(json) { //创建jsonp回调函数
      oHead.removeChild(oScript);
      clearTimeout(oScript.timer);
      window[callbackName] = null;
      options.success && options.success(json);  //先删除script标签,实际上执行的是success函数
    };
    oScript.src = options.url + '?' + callbackName;  //发送请求
    if (options.time) { //设置超时处理
      oScript.timer = setTimeout(function () {
        window[callbackName] = null;
        oHead.removeChild(oScript);
        options.fail && options.fail({ message: "超时" });
      }, options.time);
    }
  };

这是我自己写的一个原生js实现jsonp获取跨域数据的方法。

我们只需要调用jsonp函数就能够跨域获取数据了。比如:

jsonp({
    url: '/b.com/b.json',
    success: function(d){
      //数据处理
    },
    time: 5000,
    fail: function(){
      //错误处理
    }
  })

小结

再说几点注意的地方:

使用jsonp方法时,在控制台的network-JS中才能找到调用的接口,不再是XHR类了。由于页面渲染的时候script只执行一次,而且动态数据需要多次调用,所以在插入使用之后需要删除,并且要初始化回调函数。原生js实现时,最好加一个请求超时的功能,方便调试。

总之jsonp就是一种获取跨域json数据的方法。

(0)

相关推荐

  • js数组如何添加json数据及js数组与json的区别

    JSON(JavaScript Object Notation )是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,JSON是JavaScript原生数据格式. 下面给大家介绍js数组添加json数据的两种方式. // 第一种方式 personInfo : [], for(var i = 0; i < _STAGE.passengerInfoArray.length; i++){ var name = _STAGE.passengerInfoArray[i]; var person =

  • 谈谈JSON对象和字符串之间的相互转换JSON.stringify(obj)和JSON.parse(string)

    在Firefox,chrome,opera,safari,ie9,ie8等高级浏览器直接可以用JSON对象的stringify()和parse()方法. JSON.stringify(obj)将JSON转为字符串.JSON.parse(string)将字符串转为JSON格式: var a={"name":"tom","sex":"男","age":"24"}; var aToStr =

  • PHP去掉json字符串中的反斜杠\及去掉双引号前的反斜杠

    通过AJAX传到PHP的json字符串有时候加上反斜杠"\"来转义,PHP处理时需要先去掉反斜杠,然后再json_decode. $str = stripslashes($_POST['json']); $arr = json_decode($str,true); PS:php get抓取json怎样去除双引号前面的反斜杠 你这个不算标准的JSON格式数据,可以先将\"替换成"即可. 再用json_decode()系统函数将其转为json对象,如需转为数组加上第二个

  • JSON在ASP.NET中使用方法

    Json.NET的简单介绍 首先介绍一个为方便在.NET中使用JSON的API,Json.NET.它方便我们读取从浏览器流向服务器的JSON对象,也方便在响应流中写入JSON对象. Json.NET只提供了服务器端的方法,主要有实现JSON文本与XML互相转换的类,有自定义读写JSON的JsonReader类和JsonWriter类,还有一个非自定义读写JSON的JavaScriptSerializer类. ASP.NET AJAX中,服务器端由JavaScriptSerializer类的几个方

  • JSON用法之将PHP数组转JS数组,JS如何接收PHP数组

    首先下载下面这个文件(这是一段是别人写出来专门解析json的代码),然后引入这个文件! http://pan.baidu.com/s/1dD8qVr7 现在当我们需要用ajax与后台进行交互时,怎样将php的数组传送到js文件并且并js所识别? 先看php文件,当我们获取到$arr这个数组后 foreach ($arr as $value) { $json .= json_encode($value) . ','; } echo '[' . substr($json,0,strlen($json

  • JS处理json日期格式化问题

    起因 对于从C#返回的日期字段,当进行JSON序列化后,在前台JS里显示的并不是真正的日期,这让我们感觉很不爽,我们不可能为了这东西,把所有日期字段都变成string吧,所以,找了一个JS的扩展方法,来实现这个功能 实现 function ChangeDateFormat(jsondate) { jsondate = jsondate.replace("/Date(", "").replace(")/", ""); if (j

  • 解析javascript 数组以及json元素的添加删除

    javasscript删除数组的3种方法1,用shift()方法shift:删除原数组第一项,并返回删除元素的值:如果数组为空则返回undefinedvar chaomao=[1,2,3,4,5]var chaomao.shift()//得到1alert(chaomao)//[2,3,4,5] 2,用pop()方法pop:删除原数组最后一项,并返回删除元素的值:如果数组为空则返回undefinedvar chaomao=[1,2,3,4,5]var chaomao.pop()//得到5alert

  • jQuery通过Ajax向PHP服务端发送请求并返回JSON数据

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.易于人阅读和编写,同时也易于机器解析和生成.JSON在前后台交互的过程中发挥着相当出色的作用. 服务端PHP读取MYSQL数据,并转换成JSON数据,传递给前端Javascript,并操作JSON数据.本文将通过实例演示了jQuery通过Ajax向PHP服务端发送请求并返回JSON数据.阅读本文的读者应该具备jQuery.Ajax.PHP相关知识,并能熟练运用. XHTML <ul id="use

  • android针对json数据解析方法实例分析

    本文实例讲述了android针对json数据解析方法.分享给大家供大家参考.具体如下: JSON的定义: 一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性.业内主流技术为其提供了完整的解决方案(有点类似于正则表达式 ,获得了当今大部分语言的支持),从而可以在不同平台间进行数据交换.JSON采用兼容性很高的文本格式,同时也具备类似于C语言体系的行为. – Json.org JSON Vs XML 1.JSON和XML的数据可读性基本相同 2.JSON和XML同样拥有丰富的解析手段 3.

  • 使用Jquery+Ajax+Json如何实现分页显示附JAVA+JQuery实现异步分页

    先给大家展示下运行效果图:  1.后台action产生json数据. List blackList = blackService.getBlackInfoList(mobileNum, gatewayid, startDate, endDate); int totalRows = blackList.size(); StringBuffer sb = new StringBuffer(); sb.append("{\"totalCount\":\""+to

  • Django中模型Model添加JSON类型字段的方法

    本文实例讲述了Django中模型Model添加JSON类型字段的方法.分享给大家供大家参考.具体如下: Django里面让Model用于JSON字段,添加一个JSONField自动类型如下: class JSONField(models.TextField): __metaclass__ = models.SubfieldBase description = "Json" def to_python(self, value): v = models.TextField.to_pytho

  • AJAX的跨域与JSONP(为文章自动添加短址的功能)

    什么是AJAX的跨域请求 出于安全的考虑,如果你要从www.a.com通过Ajax来请求另外一个网站www.b.com的内容,浏览器是不允许你这样做的(不理解这里的安全是指什么?想想如果没有这个限制的话,黑客可以做些什么).那什么样的情况下算是跨域?域名不同那当然算是跨域了,例如a.com向b.com发送请求,这当然就是跨域了,不允许的.不过子域名不同(例如sub.a.com向www.a.com发送请求)甚至是同域名不同端口(例如a.com:80向a.com:8080)也算是跨域的. 下面演示一

随机推荐