说明:

在下面的样例中(使用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;
      
    • 效果

参考资料: