JSONP跨域
为什么要使用JSONP
有时候我们需要使用一些数据,而这些数据可以直接由一个数据源提供给我们,我们直接获取这个数据源的数据这样就省去了我们生产数据的时间。而在一些情况下,这些数据源并不在我们当前的服务器下,而因为浏览器的同源策略(正常情况下我们,我们不能从不同协议||不同主机||不同端口的数据源获取数据),我们不能正常的获取到数据,为了绕开浏览器的同源策略,才有了JSONP。
JSONP的全称为JSON with Padding即通过创建一个<script>标签添加到我们的页面中,通过标签的src属性去拉取我们需要的数据嵌入到我们的页面中,然后利用拉取到的数据去做一些该做的事情。这种做法的原因是因为<script>标签不受浏览器同源策略的影响,它可以通过src属性加载任何数据。
同源下的数据获取
在同源的情况下我们利用AJAX去异步的拉取服务器上面的数据的过程如下。
$.ajax({
url:"view!getViewList.action",
type:"post"
}).done(function(data){
console.log(data)
}).fail(function(){
console.log("request is error!")
})
我们事先定义好请求成功时的处理函数,在同源的情况下可以正常的获取到数据当AJAX在服务器上获取到数据时,则开始执行我们定义好的处理函数,并且把获取到的数据作为参数传递进去。首先我们的目的就是获取数据,然后操作数据,不管在同源还是不同源的情况下我们都是这样的目的,唯一的不同就是在不同源的情况下我们不能获取到数据,但是我们仍然可以定义好处理数据的流程。而<script>标签可以忽视浏览器的同源策略,可以在任何地方取到数据,这样我们有了处理数据的流程代码,又通过<script>标签获取到了数据,也完美的实现了我们的目的---获取数据、操作数据。
JSONP原理
先看一段简单的代码
function handler(data){ alert(data); } handler(1);
很简单!定义了一个有参的函数,然后执行这个函数同时把1作为实际参数传递进去。
我们可以稍微对上面的代码做一点改造。
<script> function handler(data){ alert(data) } </script> <script src="handler.txt"></script>
在第一个<script>标签中我们定义好函数,第二个<script>标签去请求handler.txt的内容并把内容嵌入到我们当前页面中,handler.txt的内容为handler(1),这样页面中仍然可以弹出1。
这也就是JSONP的工作原理了,我们首先定义好处理函数,然后由<script>标签去请求数据,只不过<script>标签获取到的内容并不是单纯的数据而是对我们已经定义好的数据处理函数的一个调用,在执行函数的时候把数据作为参数传进处理函数中。
http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=js 这是一个可以获取我们当前城市的一个数据接口,我们通过AJAX是不能够获取到数据的,但是我们可以通过<script src="http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=js"></script>来获取到数据。
<script src="http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=js"></script>
<script>
console.log(remote_ip_info.city)
</script>
现在基本上的问题已经都没了,但是还有一个问题就是前后台交互的问题,比如我们在定义好了一个名称为handler的处理函数,此时我们希望服务器给我们返回的就是一个handler(data),如果我们定义了一个名称为outputData的函数,我们就希望服务器返回的是一个outputData(data),所以为了方便我们的交互可以采用下面的方式:
<script> function handler(data){ alert(data); } </script> <script src="view!getViewList.action?callback=handler">
前后台协商好一个属性名,我们把定义的函数名称作为作为属性值传递给服务器,服务器获取到我们在前台定义的函数名称,并且把数据包裹在里面,一起发送到前台。
前台定义如下:
<script> function fn(data){ for(var i=0,il=data.length;i<il;i++){ console.log(data[i].viewname); } } </script> <script src="front!Test.action?callback=fn"></script>
服务器处理如下:
public String getCallback() { return callback; } public void setCallback(String callback) { this.callback = callback; } public void Test() throws Exception{ allview = getServMgr().getFrontService().showView(currentPage); views = list2json(allview); WriteJSON(callback+"("+views+")"); }
为了更加简化我们的开发,我们可以封装一个简单的函数来处理我们跨域的数据请求:
<script> var JSONP = JSONP || {}; JSONP.count = 0; (function(){ JSONP.init = function(url,callback){ this.count++;//存在多个请求是不发生冲突 var _src = ""; this.callbackName = "JSONP.fn"+this.count;//定义处理函数的函数名称,采用JSONP.XX的方式是为了可以删除
//如果url中已经有了参数设置则添加参数设置,如果没有参数则为url设置参数 url.indexOf("?")>-1?_src = url+"&callback="+this.callbackName:_src = url+"?callback="+this.callbackName; var _script = document.createElement("script"); _script.src = _src;
//该函数和this.callbackName想对应方便删除 JSONP["fn"+this.count] = function(data){ callback(data); } document.getElementsByTagName("head")[0].appendChild(_script); } JSONP.done = function(url,callback){ this.init(url,callback); var allScripts = document.getElementsByTagName("script");
//处理函数执行完成以后删除函数和标签 allScripts[0].parentNode.removeChild(allScripts[allScripts.length-1]); setTimeout(function(){delete this["fn"+this.count]},0);//确保函数执行完成之后才删除 } }()) </script>