深入分析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参数的小问题
Mar 02 Javascript
Javascript 强制类型转换函数
May 17 Javascript
JavaScript与DOM组合动态创建表格实例
Dec 23 Javascript
JS获取页面input控件中所有text控件并追加样式属性
Feb 25 Javascript
JS中的form.submit()不能提交表单的错误原因
Oct 08 Javascript
Jquery弹出层插件ThickBox的使用方法
Dec 09 Javascript
jQuery找出网页上最高元素的方法
Mar 20 Javascript
JavaScript正则表达式匹配 div  style标签
Mar 15 Javascript
JavaScript6 let 新语法优势介绍
Jul 15 Javascript
jQuery+HTML5实现弹出创意搜索框层
Dec 29 Javascript
zTree异步加载展开第一级节点的实现方法
Sep 05 Javascript
看看“疫苗查询”小程序有温度的代码
Jul 31 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验证码类
2016/09/29 PHP
Jquery 点击按钮显示和隐藏层的代码
2011/07/25 Javascript
jQuery-onload让第一次页面加载时图片是淡入方式显示
2012/05/23 Javascript
ajax java 实现自动完成功能
2012/12/19 Javascript
js购物车实现思路及代码(个人感觉不错)
2013/12/23 Javascript
BootStrap实用代码片段之一
2016/03/22 Javascript
浅谈JavaScript函数的四种存在形态
2016/06/08 Javascript
weUI应用之JS常用信息提示弹层的封装
2016/11/21 Javascript
详解Vue组件之间的数据通信实例
2017/06/17 Javascript
angular中实现li或者某个元素点击变色的两种方法
2017/07/27 Javascript
vue 文件目录结构详解
2017/11/24 Javascript
vue 实现 ios 原生picker 效果及实现思路解析
2017/12/06 Javascript
vue-cli扩展多模块打包的示例代码
2018/04/09 Javascript
当vue路由变化时,改变导航栏的样式方法
2018/08/22 Javascript
mpvue实现微信小程序快递单号查询代码
2020/04/03 Javascript
DWR内存兼容及无法调用问题解决方案
2020/10/16 Javascript
解决windows下Sublime Text 2 运行 PyQt 不显示的方法分享
2014/06/18 Python
Python的面向对象思想分析
2015/01/14 Python
python图像处理之反色实现方法
2015/05/30 Python
Python常见格式化字符串方法小结【百分号与format方法】
2016/09/18 Python
python实现自动登录后台管理系统
2018/10/18 Python
python GUI图形化编程wxpython的使用
2019/07/19 Python
Python django搭建layui提交表单,表格,图标的实例
2019/11/18 Python
Django Channel实时推送与聊天的示例代码
2020/04/30 Python
Levi’s西班牙官方网站:李维斯,著名的牛仔裤品牌
2020/08/20 全球购物
英国马莎百货印度官网:Marks & Spencer印度
2020/10/08 全球购物
计算机专业职业规划
2014/02/28 职场文书
外国人聘用意向书
2014/04/01 职场文书
施工安全责任书
2014/04/14 职场文书
信息与计算机科学职业规划范文:成为一艘有方向的船
2014/09/11 职场文书
公安纪律作风整顿剖析材料
2014/10/10 职场文书
2014年环保局工作总结
2014/12/11 职场文书
社区三八妇女节活动总结
2015/02/06 职场文书
因工资原因离职的辞职信范文
2015/05/12 职场文书
实验室安全管理制度
2015/08/05 职场文书
提档介绍信范文
2015/10/22 职场文书