深入分析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 相关文章推荐
JavaScript中this关键字使用方法详解
Mar 08 Javascript
jQuery MD5加密实现代码
Mar 15 Javascript
js replace 与replaceall实例用法详解
Aug 03 Javascript
JavaScript中的console.profile()函数详细介绍
Dec 29 Javascript
jQuery实现内容定时切换效果完整实例
Apr 06 Javascript
JavaScript开发Chrome浏览器扩展程序UI的教程
May 16 Javascript
JavaScript必知必会(五) eval 的使用
Jun 08 Javascript
微信小程序 swiper组件轮播图详解及实例
Nov 16 Javascript
解决axios发送post请求返回400状态码的问题
Aug 11 Javascript
Vue 解决多级动态面包屑导航的问题
Nov 04 Javascript
jQuery 选择方法及$(this)用法实例分析
May 19 jQuery
React forwardRef的使用方法及注意点
Jun 13 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
php根据年月获取季度的方法
2014/03/31 PHP
基于GD2图形库的PHP生成图片缩略图类代码分享
2015/02/08 PHP
php自定义函数转换html标签示例
2016/09/29 PHP
PHP添加PNG图片背景透明水印操作类定义与用法示例
2019/03/12 PHP
不间断滚动JS打包类,基本可以实现所有的滚动效果,太强了
2007/12/08 Javascript
Span元素的width属性无效果原因及解决方案
2010/01/15 Javascript
表格单元格交错着色实现思路及代码
2013/04/01 Javascript
浅析JS刷新框架中的其他页面 &amp;&amp; JS刷新窗口方法汇总
2013/07/08 Javascript
javascript中for/in循环及使用技巧
2015/09/01 Javascript
JavaScript必看小技巧(必看)
2016/06/07 Javascript
javascript中获取class的简单实现
2016/07/12 Javascript
很棒的js选项卡切换效果
2016/07/15 Javascript
Zabbix添加Node.js监控的方法
2016/10/20 Javascript
javascript入门之window对象【新手必看】
2016/11/22 Javascript
Vue.js学习之过滤器详解
2017/01/22 Javascript
WebPack配置vue多页面的技巧
2018/05/15 Javascript
Vue+Jwt+SpringBoot+Ldap完成登录认证的示例代码
2018/05/21 Javascript
快速解决vue-cli在ie9+中无效的问题
2018/09/04 Javascript
js实现下拉框二级联动
2018/12/04 Javascript
详解vuejs2.0 select 动态绑定下拉框支持多选
2019/04/25 Javascript
微信小程序npm引入vant-weapp的踩坑记录
2019/08/01 Javascript
Vue通过provide inject实现组件通信
2020/09/03 Javascript
[06:09]辉夜杯主赛事开幕式
2015/12/25 DOTA
Python新手入门最容易犯的错误总结
2017/04/24 Python
SVM基本概念及Python实现代码
2017/12/27 Python
深入解析HTML5 Canvas控制图形矩阵变换的方法
2016/03/24 HTML / CSS
小米旗下精品生活电商平台:小米有品
2018/12/18 全球购物
迪卡侬印尼体育用品商店:Decathlon印尼
2020/03/11 全球购物
Three Graces London官网:英国奢侈品牌
2021/03/18 全球购物
Why we need EJB
2016/10/20 面试题
市场营销专科应届生求职信
2013/11/24 职场文书
思想汇报格式
2014/01/05 职场文书
工资收入证明
2014/10/07 职场文书
爱国电影观后感
2015/06/19 职场文书
小型婚礼主持词
2015/06/30 职场文书
导游词之广东佛山(南风古灶)
2019/09/24 职场文书