深入分析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居然支持中文(unicode)编程!
Apr 12 Javascript
prototype Element学习笔记(Element篇三)
Oct 26 Javascript
jquery $.ajax入门应用一
Nov 19 Javascript
javascript针对DOM的应用分析(二)
Apr 15 Javascript
jquery实现手风琴效果
Nov 20 Javascript
jQuery mobile转换url地址及获取url中目录部分的方法
Dec 04 Javascript
Bootstrap实现水平排列的表单
Jul 04 Javascript
JS获取随机数和时间转换的简单实例
Jul 10 Javascript
ES6 javascript中Class类继承用法实例详解
Oct 30 Javascript
利用Angular2 + Ionic3开发IOS应用实例教程
Jan 15 Javascript
JS数组求和的常用方法总结【5种方法】
Jan 14 Javascript
超详细动手搭建一个VuePress 站点及开启PWA与自动部署的方法
Jan 27 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三元运算符的结合性介绍
2012/01/10 PHP
解析mysql 表中的碎片产生原因以及清理
2013/06/22 PHP
php数组保存文本与文本反编成数组实例
2014/11/13 PHP
flash javascript之间的通讯方法小结
2008/12/20 Javascript
Jquery替换已存在于element上的event的方法
2010/03/09 Javascript
jQuery响应鼠标事件并隐藏与显示input默认值
2014/08/24 Javascript
chrome调试javascript详解
2015/10/21 Javascript
Javascript设计模式之观察者模式(推荐)
2016/03/29 Javascript
AngularJS基础 ng-readonly 指令简单示例
2016/08/02 Javascript
angularjs 源码解析之scope
2016/08/22 Javascript
基于jQuery实现火焰灯效果导航菜单
2017/01/04 Javascript
Javascript前端经典的面试题及答案
2017/03/14 Javascript
jQuery简单绑定单个事件的方法示例
2017/06/10 jQuery
解决Extjs下拉框不显示的问题
2017/06/21 Javascript
js中less常用的方法小结
2017/08/09 Javascript
React教程之Props验证的具体用法(Props Validation)
2017/09/04 Javascript
浅谈Vue数据绑定的原理
2018/01/08 Javascript
node实现的爬虫功能示例
2018/05/04 Javascript
vue基础之使用get、post、jsonp实现交互功能示例
2019/03/12 Javascript
微信小程序实现打开并下载服务器上面的pdf文件到手机
2019/09/20 Javascript
vue中使用[provide/inject]实现页面reload的方法
2019/09/30 Javascript
Vue修改项目启动端口号方法
2019/11/07 Javascript
完美解决python中ndarray 默认用科学计数法显示的问题
2018/07/14 Python
利用pyuic5将ui文件转换为py文件的方法
2019/06/19 Python
Python多版本开发环境管理工具介绍
2019/07/03 Python
教你如何编写、保存与运行Python程序的方法
2019/07/12 Python
Python多重继承之菱形继承的实例详解
2020/02/12 Python
Python中如何添加自定义模块
2020/06/09 Python
基于python模拟TCP3次握手连接及发送数据
2020/11/06 Python
适合各种场合的美食礼品:Harry & David
2016/08/03 全球购物
中国领先的汽车保养服务平台:途虎养车
2019/10/18 全球购物
Java程序开发中如何应用线程
2016/03/03 面试题
入党申请自荐书范文
2014/02/11 职场文书
2016年入党心得体会范文
2016/01/23 职场文书
MySQL连表查询分组去重的实现示例
2021/07/01 MySQL
Python编写冷笑话生成器
2022/04/20 Python