深入分析jsonp协议原理


Posted in Javascript onSeptember 26, 2015

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

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

JSON和JSONP的区别

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

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

JSONP详细阐述

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

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

深入分析jsonp协议原理

那么该如何解决呢?使用框架的前端童鞋们可能都有自己相应的办法,比如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数据的方法。

Javascript 相关文章推荐
js传值 判断
Oct 26 Javascript
javascript之对系统的toFixed()方法的修正
May 08 Javascript
JavaScript 小型打飞机游戏实现原理说明
Oct 28 Javascript
利用JS实现浏览器的title闪烁
Jul 08 Javascript
JavaScript实现函数返回多个值的方法
Jun 09 Javascript
Javascript常用小技巧汇总
Jun 24 Javascript
javascript实现加载xml文件的方法
Nov 24 Javascript
jQuery EasyUI 获取tabs的实例解析
Dec 06 Javascript
微信小程序中顶部导航栏的实现代码
Mar 30 Javascript
vue按需加载组件webpack require.ensure的方法
Dec 13 Javascript
node.js使用免费的阿里云ip查询获取ip所在地【推荐】
Sep 03 Javascript
ES6 如何改变JS内置行为的代理与反射
Feb 11 Javascript
如何用jQuery实现ASP.NET GridView折叠伸展效果
Sep 26 #Javascript
angularjs学习笔记之双向数据绑定
Sep 26 #Javascript
JS非Alert实现网页右下角“未读信息”效果弹窗
Sep 26 #Javascript
angularjs学习笔记之完整的项目结构
Sep 26 #Javascript
jQuery实现的登录浮动框效果代码
Sep 26 #Javascript
angularjs学习笔记之三大模块(modal,controller,view)
Sep 26 #Javascript
JS实现可拖曳、可关闭的弹窗效果
Sep 26 #Javascript
You might like
老机欣赏|中国60年代精品收音机
2021/03/02 无线电
PHP 命令行参数详解及应用
2011/05/18 PHP
php中的静态变量的基本用法
2014/03/20 PHP
php函数mkdir实现递归创建层级目录
2016/10/27 PHP
PHP编程计算日期间隔天数的方法
2017/04/26 PHP
PhpStorm本地断点调试的方法步骤
2018/05/21 PHP
用Javascript实现Sleep暂停功能代码
2010/09/03 Javascript
一款jquery特效编写的大度宽屏焦点图切换特效的实例代码
2013/08/05 Javascript
点评js异步加载的4种方式
2015/12/22 Javascript
javascript中FOREACH数组方法使用示例
2016/03/01 Javascript
基于jQuery实现页面搜索功能
2020/03/26 Javascript
20分钟打造属于你的Bootstrap站点
2016/07/27 Javascript
jQuery插件zTree实现删除树节点的方法示例
2017/03/08 Javascript
jQuery插件ContextMenu自定义图标
2017/03/15 Javascript
快速掌握jquery分页插件jqPaginator的使用方法
2017/08/09 jQuery
AngularJs+Bootstrap实现漂亮的计算器
2017/08/10 Javascript
jQuery 禁止表单用户名、密码自动填充功能
2017/10/30 jQuery
小程序图片长按识别功能的实现方法
2018/08/30 Javascript
vue+iview/elementUi实现城市多选
2019/03/28 Javascript
layui中的switch开关实现方法
2019/09/03 Javascript
[16:04]DOTA2海涛带你玩炸弹 9月5日更新内容详解
2014/09/05 DOTA
Python中使用动态变量名的方法
2014/05/06 Python
Python正则表达式的使用范例详解
2014/08/08 Python
Python输出带颜色的字符串实例
2017/10/10 Python
如何基于python实现不邻接植花
2020/05/01 Python
Python如何用wx模块创建文本编辑器
2020/06/07 Python
澳洲的服装老品牌:SABA
2018/02/06 全球购物
如何进行Linux分区优化
2016/09/13 面试题
工商管理专业实习生自我鉴定
2013/09/29 职场文书
有关打架的检讨书
2014/01/25 职场文书
再婚婚前财产协议书范本
2014/10/19 职场文书
2014年乡镇工会工作总结
2014/12/02 职场文书
2016年社区中秋节活动总结
2016/04/05 职场文书
2019通用版新员工入职培训方案!
2019/07/11 职场文书
解决tk mapper 通用mapper的bug问题
2021/06/16 Java/Android
浅谈Redis缓冲区机制
2022/06/05 Redis