title: js中的页面间通信
date: 2016-8-10
tag: javascript


js中的页面间通信

本来准备找实习了,由于暑假回家学车,学完后回不去学校,(⊙﹏⊙)。
一些找前端实习的小伙伴都被问到了页面间通讯的问题,所以做了一点了解。
页面间通信涉及到跨域与不跨域,不跨域的通信比较简单,而跨域通信的方法也有,不过每一种也都有限制。

不跨域的通信

1.1 iframe嵌套

有些页面会通过iframe标签嵌套页面,嵌套页面即作为子页面。父与子页面之间的通信很简单。

<!-- 父页面 postMessage.html -->
<html>
    <head></head>
    <body>
        <input id="msg" type="text" placeholder="再次输入信息"/>
        <input type="button" value="发送消息" onclick="sendMsg()"/>
        <!-- 子页面 getMessage.html -->
        <iframe src="getMessage.html"></iframe>
    </body>
</html>

<!-- 子页面 getMessage.html-->
<html>
    <head></head>
    <body>
        <h3>这里是getMessage.html</h3>
        <div id="show-msg"></div>
    </body>
</html>

代码:

<script>
    function sentMsg(){
        var msg = document.querySelector('#msg');
        var data = msg.value;
        window.frames[0].document.querySelector('#show-msg').innerHTML = data;
    }
</script>  

在父页面中的input中输入内容,点击发送,在子页面中就会显示对应内容。
当然,如果父页面中存在多个iframe,只需通过window.frame找到对应的ifrmae,如上传递内容就可以了。

1.2 跨标签页通信 window.open

有时候,当在一个页面中点击链接,打开了一个新的页面,两个页面在相同的域下,可以实现标签页之间的通讯.

<!-- 父页面 postMessage.html -->
<html>
    <head></head>
    <body>
        <input type="button" value="发送消息" onclick="sendMsg()"/>
        <a href="getMessage.html" target="_blank" onclick="toPage(event)">点击</a>
    </body>
</html>

代码:

<!-- 父页面 postMessage.html -->
<script>
    var newPage;
    function toPage(evt){
        evt.preventDefault(); //阻止默认事件
        var url = evt.target.getAttribute('href');
        newPage = window.open(url,'newPage');
    }
    function sentMsg(){
        newPage.document.querySelector('#msg').innerHTML = '这是postMessage发来的信息';
    }
</script>

<!-- 子页面 getMessage.html -->
<script>
    window.addEventListener('load',function(evt){
        opener.document.querySelector('#show-msg').innerHTML = 'load好子页面了';
    },false);
</script>  

当在postMessage.html页面里点击链接,通过window.open方法打开一个新的页面,如父子页面满足”同源策略”则该方法会返回创建窗口的window对象的引用。getMessage.html添加侦听load事件,通过window.opener获取创建该窗口的window对象的引用,通过该引用可以控制该页面里元素。

关于window.open()和window.opener()参见MDN的window.openwindow.opener

跨域通信

有时候我们希望能够在不同的域名之间传递数据,但由于浏览器的“同源策略”,不同域之间的通信只能通过一些技巧来实现。以下是对是否是同源情况的判断:


编号
URL
说明
是否允许通信


1
http://www.a.com/a.js
http://www.a.com/b.js
同一域名下
允许


2
http://www.a.com/a/a.js
http://www.a.com/b/b.js
同一域名下不同文件夹
允许


3
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名,不同端口
不允许


4
http://www.a.com/a.js
https://www.a.com/b.js
同一域名,不同协议
不允许


5
http://www.a.com/a.js
http://127.0.0.1/b.js
域名和域名对应ip
不允许


6
http://www.a.com/a.js
http://will.a.com/b.js
主域相同,子域不同
不允许


7
http://www.a.com/a.js
http://a.com/b.js
同一域名,不同二级域名(同上)
不允许(cookie这种情况下也不允许访问)


8
http://www.a.com/a.js
http://www.b.com/b.js
不同域名
不允许


一般跨域传递数据有3种方法,jsonp、document.domain、window.name来实现。不过,这三种方法都有各自的限制。

####2.1 jsonp实现
jsonp全称是”json padding”,json想必已经很清楚了,如果忘了就点击自己重新复习吧。JSONP是一种使用JSON数据的方式,返回的不是JSON对象,是包含JSON对象的javaScript脚本。
由于常规使用XMLHttpRequest请求只允许同域下的资源,但是script标签加载js文件却可以跨域加载,比如img标签也可以。通过使用script标签来进行跨域请求,并在响应中返回要执行的script代码,其中可以直接使用JSON传递 javascript对象。即在跨域的服务端生成JSON数据,然后包装成script脚本回传,就不用突破同源策略的限制,解决了跨域访问的问题。

<script>
    function callback(data){
        //处理数据

    }
    var jsonP = document.createElement('script');
    jsonP.src = 'http://demo.com/server.php?req=callback';//域名随意起的
    document.body.appendChild(jsonP);
</script>

服务器端的响应代码:

<?php 
    $req = $_GET['req'];
    $data = array(
        'data'=>'这是请求的数据'
    );
    echo $req."(json_encode($data))";
?>

这其实就是JSONP的简单实现。客户端创建一个回调函数,然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递,完成回调。即将服务器的json数据填充(padding)作为回调函数的参数。
jsonp方式跨域也有缺陷,首先只能是通过GET方式请求数据,若操作成功,就顺利执行。出现失败则不会有提示,例如不能从服务器捕捉到 404 错误,也不能取消或重新开始请求。当然超时后就不用管他了。同时也需要服务器相应的配合,处理请求才行。

####2.2 document.domain
每一个页面都有document.domain属性,调用这个属性,返回当前文档的域名。也可以对该属性进行赋值,不过值必须是包含基础域名的值(必须属于同一个基础域名!而且所用的协议,端口都要一致)。
例如:

//http://willing.a.com/a.html与http://will.a.com/b.html
//a.html
<html>
    <head>
        <script>
            docuemnt.domain = 'a.com';
        </script>
    </head>
    <body>
    <frame src="http://will.a.com/b.html"></iframe>
    </body>
</html>

//b.html
<html>
    <head>
        <script>
            docuemnt.domain = 'a.com';
        </script>
    </head>
    <body></body>
</html>

两个页面处于同一个基础域名下不同子域名下,他们之间js跨域(子域)相互调用,需要在head标签中显式设置同一基础域名,否则会失败。
通过这种方式,可以实现跨子域的数据传递,而无法实现跨基础域名的数据传递。
另外,Html5的localStorage也可以通过设置document.domain实现跨子域的数据传递。

####2.3 window.name
每个标签在打开的时候都会存在window.name属性,若没有设置一般为空。window.open函数调用时传递的第二参数即为指定创建窗口的name值。
浏览器中当一个标签页打开到关闭之间,window.name的值不变,即使在期间访问了不同域名的网站,name值都是空或手动设置的值。在浏览器控制台输入代码:

window.name = 'YouGuess';
location.href = 'http://www.qq.com';  

在一个页面中通过iframe嵌入的跨域页面,也可以实现跨域传值,不过该值的大小有所限制(大约2M)。

<!-- 父页面 postMessage.html -->
<html>
    <head></head>
    <body>
        <div id="show-msg"></div>
        <input type="button" value="获取子页面name" onclick="getChildName()"/>
        <!-- 子页面 getMessage.html -->
        <iframe src="www.baidu.com"></iframe>
    </body>
</html>

<!-- 子页面 getMessage.html-->
<html>
    <head></head>
    <body>

    </body>
</html>  

代码:

<script>
    <!-- 父页面 postMessage.html -->
    function getChildName(){
        var show = document.querySelector('#show-msg');
        var childName = window.frames[0].contentWidth.name;
        show.innerHTML = childName;
    }

    <!-- 子页面 getMessage.html-->
    window.addEventListener('load',function(){
        window.name = 'YouGuess';
    })
</script>  

当在iframe中的页面设置好name的值,点击父页面的按键,即可显示子页面的name属性。

###3. HTML5的postMessage
HTML5提供了postMessage方法,该方法既可以跨域通信也可以用来不跨域的通信,不过在IE中目前只支持IE8+,其他浏览器基本全面支持。

otherWindow.postMessage(data,origin)
-targetSrc指目标窗口或iframe
-data指要传递的数据,html5规范可以使js的任意基本类型和可复制对象(ie8、9除外)
-origin指明目标窗口的源(协议+主机+端口号[+URL],URL会被忽略,所以可以不写)定和当前窗口同源的话设置为”/“,若”*”则代表任何页面。