Javascript跨域的几种方式
说明:
在下面的样例中(使用Tomcat做服务器),需要修改以下配置:
tomcat/conf/server.xml
<Connector port="80" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Host name="www.example.com" appBase="webapps"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
</Host>
<Host name="www.a.com" appBase="webapps_a"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
</Host>
<Host name="www.b.com" appBase="webapps_b"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
</Host>
<Host name="qqq.a.com" appBase="webapps_a_c"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
</Host>
C:\Windows\System32\drivers\etc\hosts
127.0.0.1 www.example.com
127.0.0.1 example.com
127.0.0.1 www.a.com
127.0.0.1 www.b.com
127.0.0.1 qqq.a.com
一、使用JSONP
1、原理
利用<script>
标签绕过同源策略,从服务器获得类似:jsonpcallback({"name":"Jack","from":"BJ"})
的数据,其中jsonpcallback
是在客户端页面存在的回调函数,该函数的参数即为从服务器返回的json数据。
2、实现
-
客户端
以下几种方式均可(使用jQuery):
$.ajax
$.ajax({ url: 'http://localhost/Web/test?callback=?', dataType: 'jsonp', data: {username:'wzk'}, success: function(data){ //do something } });
$.getJSON
$.getJSON( 'http://localhost/Web/test?callback=?', {username:'wzk'}, function(data){ //do something } );
$.doGet
$.doGet({ url: "http://localhost/Web/test", dataType: 'jsonp', jsonp: 'callback', jsonpCallback: 'cfun', success: function(data){ //do something } });
$.doPost
$.doPost({ url: "http://localhost/Web/test", dataType: 'jsonp', data: {username:'wzk'}, jsonp: 'callback', jsonpCallback: 'cfun', success: function(data){ //do something } });
-
服务端
使用Servlet:
String funname =request.getParameter("callback");
PrintWriter out = response.getWriter();
out.write(funname + "({result:'success',data:{name:'jack',age:12}})");
out.flush();
out.close();
3、样例
www.a.com/basic.html
<script src="http://www.example.com/Server/jquery-2.1.3.js"></script>
<p>Ajax请求到的数据:</p>
<ul id="show"></ul>
<script>
function showData(data){
alert("数据(文件加载成功!)");
var ul = $("#show"),str = "请求到的书籍信息:<br/>";
$.each(data.books,function(index,item){
str += "<li>" + (index + 1) + " " + item.name + " " + item.price + "</li>";
});
ul.html(str);
}
</script>
<script src="http://www.example.com/Server/JsonServlet?callback=showData"></script>
www.example.com/Server/JsonServlet
public class JsonServlet extends HttpServlet
{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setContentType("application/javascript;charset=UTF-8");
PrintWriter out = response.getWriter();
String callbackName = request.getParameter("callback");
String jsonData = "{\"result\":\"success\",\"books\":[{\"name\":\"Java开发\",\"price\":12.5},{\"name\":\"JS实战\",\"price\":31.9}]}";
out.write(callbackName + "(" + jsonData + ")");
out.flush();
out.close();
}
}
- 效果
二、使用document.domain
1、适用场合
需要在不同子域的框架(iframe)中互相访问其JS对象时。例如:a.example.com(或example.com)的a.html中以iframe方式嵌入b.example.com的b.html,如果需要在a.html和b.html中使用js操作彼此对象,需要在a.html和b.html中设置document.domain为example.com即可。
2、使用限制
它们的主域必须相同,所用协议、端口都要一致,而且document.domain
只能设置成自身或更高一级的父域。
3、样例
www.a.com/domain/a.html
document.domain = "a.com";
window.onload = function(){
var cdoc = document.cIframe.document;
cdoc.getElementById("cmsg").innerHTML = "<font style='background-color:#7FFF00;'>此处由www.a.com添加!</font>";
};
<body>
<p id="amsg"></p>
<iframe id="cIframe" name="cIframe" src="http://qqq.a.com/domain/c.html" width="800px" height="300px"></iframe>
<body>
qqq.a.com/domain/c.html
document.domain = "a.com";
parent.document.getElementById("amsg").innerHTML = "<font style='background-color:#FFD700'>由qqq.a.com添加!</font>";
<p id="cmsg"></p>
- 效果
三、使用postMessage
可以使用window.postMessage(message,targetOrigin)
方法跨域传递数据:
该方法的第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 * 。
需要接收消息的window对象,可通过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。
-
样例
在域example.com的example.html中以iframe方式嵌入www.example.com域中的sub.html,两个页面加载的时候给彼此互相发送消息。
-
example.com:8888/examples/example.html
<script> window.onload = function(){ var swin =document.getElementById("subIframe").contentWindow; var data ={"from":"子域","msg":"Hello!"}; swin.postMessage(data,"http://www.example.com:8888"); }; window.addEventListener("message",function(e){ var data = e.data; alert("来自 " +e.origin + " 的消息!" + data.from + " " + data.msg); },false); </script> <BODY> <H3>example.com</H3> <iframe id="subIframe"src="http://www.example.com:8888/examples/sub.html"width="800px" height="300px"></iframe> </BODY>
-
www.example.com:8888/examples/sub.html
<script> window.onload = function(){ var data ={"from":"主域","msg":"HI!"}; window.parent.postMessage(data,"http://example.com:8888"); }; window.addEventListener("message",function(e){ var data = e.data; alert("来自 " + e.origin+ " 的消息! " + data.from + " " + data.msg); },false); </script>
-
效果
在访问
http://example.com:8888/examples/example.html
的时候,会依次弹出主页面和子页面收到的消息:
-
注意:postMessage在IE下只能传字符串,因此如果传递的是Object类型,需要在发送页用JSON.stringify转换成字符串,接受时再用JSON.parse转成对象。
四、使用中间iframe方式
1、使用parent.parent.xxx调用方法
在a.com的a.html以iframe形式嵌入b.com下的b.html,在b.html中需要使用a.html中的方法,此时需要在b.html中再嵌入与a.html同域的c.html,可通过在c.html中使用parent.parent.xxx来调用a.html的xxx方法。
www.a.com/iframe/a.html
function calc(a,b){
return a + "+" + b + "=" + (a + b);
}
<p id="show"></p>
<iframe id="bIframe" name="bIframe" src="http://www.b.com/iframe/b.html" width="800px" height="300px"></iframe>
www.b.com/iframe/b.html
$(function(){
$("#calcBtn").click(function(){
if($("#temp").length > 0){
$("#temp").remove();
}
$("body").append("<iframe id='temp' src='http://www.a.com/iframe/c.html' style='display:none;'></iframe>");
});
});
<button id="calcBtn">执行a.com - a.html的加法</button>
www.a.com/iframe/c.html
只需要JS即可,不需要内容
var show = parent.parent.document.getElementById("show");
show.innerHTML = "<font color='green'>" + parent.parent.calc(1,1) + "</font>";
setTimeout(function(){
show.innerHTML = "";
},5000);
alert("调用www.a.com -- a.html 的cala方法计算的结果:" + parent.parent.calc(1,1));
- 效果
2、使用window.name跨域传递数据
- 原理
window.name的值在不同的页面(甚至不同域名)加载后依旧存在。
注意:传递的数据大小因浏览器不同有不同的限制。
-
样例
a.com的app.html 中通过javascript创建iframe,并将location指向 b.com的data.html,并监听iframe的onload事件,加载完毕后修改location指向a.com的proxy.html,此时即可通过iframe.contentWindow.name获取data.html的数据。
-
www.a.com/iframe/name/a.html
$().ready(function(){ $("#btn").click(function(){ var state = 0, bIframe = document.createElement("iframe"), loadFun = function(){ if(state == 0){ state = 1; bIframe.src = "http://www.a.com/iframe/name/c.html"; }else if(state == 1){ var data = bIframe.contentWindow.name; alert("已取得b.html的数据!"); document.getElementById("show").innerHTML = "<font style='background-color:#7FFF00;'>b.html - window.name: " + data + "</font>"; bIframe.contentWindow.document.write(""); bIframe.contentWindow.close(); document.body.removeChild(bIframe); } }; //首次设置iframe的src为b.com的b.html,iframe加载时window.name设置数据,并将iframe的src改为a.com下的c.html, //当该iframe再次加载时,获取iframe.contentWindow的name属性即需要的数据 bIframe.id = "temp"; bIframe.src = "http://www.b.com/iframe/name/b.html"; bIframe.style.display = "none"; bIframe.onload = loadFun; document.body.appendChild(bIframe); }); });
<p><button id="btn">获取b.html的数据</button></p> <p id="show"></p>
-
www.b.com/iframe/name/b.html
只需要JS即可,不需要内容
window.name = "123";
-
www.a.com/iframe/name/c.html
中间页面,不需要内容
<body> www.a.com -- c.html </body>
-
效果
-
3、使用location.hash
类似于window.name方式。具体如下:a.com的a.html与b.com的b.html传递信息,在a.html中使用js创建src为b.html的iframe (src为http://www.b.com/iframe/hash/b.html#one) ,hash值为’one’,在b.html中对location.hash判断并做不同的处理,构造不同的hash值(记为B)用于传给a.html,在b.html中使用js创建src为a.com下c.html的iframe,同时该iframe的hash值为之前构造出的hash(B),在c.html中修改parent.parent.location.hash为B。在a.html中增加interval定时检测location.hash值的变化,当发生变化时取得该值即为b.html传递的数据。
-
样例
-
www.a.com/iframe/hash/a.html
$(function(){ var bIframe; var oldHash = location.hash; var chkInv = setInterval(function(){ var hash = location.hash; if(hash.length > 1 && oldHash != hash){ oldHash = hash; $("#show").html("数据: " + hash.substring(1)); } },1000); $("#change").click(function(){ if(bIframe){ //修改hash值不会触发onload事件,因此需要重新创建iframe document.body.removeChild(bIframe); } bIframe = document.createElement("iframe"); bIframe.style.display = "none"; bIframe.src = "http://www.b.com/iframe/hash/b.html#" + $("#cond").val(); document.body.appendChild(bIframe); }); });
<input id="cond" type="text"/> <button id="change">更改hash</button> <p><font style="background-color:#ADFF2F;" id="show"></font></p>
-
www.b.com/iframe/hash/b.html
只需要JS即可,不需要内容
var cIframe; window.onload = function(){ var str = "http://www.a.com/iframe/hash/c.html"; switch(location.hash){ case '#1': str += "#one!!!"; break case '#2': str += "#two@@@"; break; default: str += "#other"; break; } if(cIframe){ document.body.removeChild(cIframe); } cIframe = document.createElement("iframe"); cIframe.style.display = "none"; cIframe.src = str; document.body.appendChild(cIframe); };
-
a.com/c.html
中间页面,不需要内容
parent.parent.location.hash = location.hash;
-
效果
-