页面通信

60 阅读4分钟
  • 同源:如果两个页面的协议,域名和端口都相同,则两个页面具有相同的源。
  • 同源策略:它是浏览器提供的一个安全功能,通俗的理解:浏览器规定,A网站的JavaScript,不允许和非同源的网站C之间,进行资源的交互。
  • 跨域:两个URL的协议、域名、端口一项或多项不一样。

一、同源页面通信

1.BroadcastChannel

首页新建广播频道,子页建立相同名称的频道,并通过message监听即可。只要postMessage每次触发,就会监听到。

语法

创建一个标识为xixi的频道:

const bc = new BroadcastChannel('xixi');

各个页面可以通过onmessage来监听被广播的消息

bc.onmessage = function (e) {
    const data = e.data;
    const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
    console.log('[BroadcastChannel] receive message:', text);
};

要发送消息时只需要调用实例上的postMessage方法即可:

bc.postMessage(mydata);

使用

  • 第一个页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="button">点击</button>
    <script>
        let button=document.getElementById('button')
        button.onclick=function(){
            let cast = new BroadcastChannel('mychannel'); //创建一个名字是mychannel的对象。记住这个名字,下面会用到
            myObj = { from: "children1", content: "add" };
            cast.postMessage(myObj);
        }
       
    </script>
</body>
</html>
  • 第二个页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        let cast1 = new BroadcastChannel('mychannel');//创建一个和刚才的名字一样的对象
        cast1.onmessage = function (e) {
            //e.data就是刚才的信息; 
            console.log(e.data);
        };
    </script>
</body>
</html>

在第一个页面点击按钮,在第二个页面的控制台上即可查看第一个页面发送过来的信息。

补充

  • 必要的情况可以将触发事件绑定到window,子组件通过 window.parent.事件名 触发

2.storage事件监听

当同源页面的某个页面修改了localStorage/sessionStorage,其余的同源页面只要注册了storage事件,就会触发,所以满足触发监听的条件有三个:

  • 同一浏览器打开了两个同源页面
  • 其中一个网页修改了localStorage
  • 另一网页注册了storage事件

注意:在同一个网页修改本地存储,又在同一个网页监听,这样是没有效果的。

使用

基本用法

// A页面
window.addEventListener('storage', function (e) {
  console.log('11111111', e);
});

// B页面
localStorage.clear();
localStorage.setItem('key123', 'value123');

根据传值让B页面控制A页面刷新

// A页面
window.addEventListener('storage', function (e) {
  if(e.key ==='key123'){
      window.location.reload()
  }
});

// B页面
localStorage.setItem('key123', 'value123');

如果非得要在同一网页监听怎么办?可以重写localStorage的方法,如下:

var orignalSetItem = localStorage.setItem;
localStorage.setItem = function (key, newValue) {
    var setItemEvent = new Event("setItemEvent");
    setItemEvent.newValue = newValue;
    window.dispatchEvent(setItemEvent);
    orignalSetItem.apply(this, arguments);
}
window.addEventListener("setItemEvent", function (e) {
    console.log(e.key + '---' + e.newValue);
});
localStorage.setItem("aaa", "111");

二、跨域页面通信

1.postMessage

postMessage 可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递
  • 页面与嵌套的 iframe 消息传递
  • 多窗口之间消息传递

语法

otherWindow.postMessage(message, targetOrigin, [transfer]);
  • otherWindow:其他窗口的一个引用,比如 iframe 的 contentWindow 属性、执行 window.open 返回的窗口对象、或者是命名过或数值索引的 window.frames。

  • message:要发送的数据。它将会被结构化克隆算法序列化,所以无需自己序列化(部分低版本浏览器只支持字符串,所以发送的数据最好用JSON.stringify() 序列化)。

  • targetOrigin:通过 targetOrigin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串“*”(表示无限制)或者一个 URI(如果要指定和当前窗口同源的话可设置为"/")。在发送消息的时候,如果目标窗口的协议、主机地址或端口号这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会发送。

  • 执行如下代码,其他 window 可以监听派遣的 message 获取发送过来的数据:

window.addEventListener("message", (event)=>{
   var origin = event.origin
   if (origin !== "http://example.org:8080")
     return;
   // ...
}, false);
  • 使用 addEventListener 绑定事件,如果代码走多次,会导致监听绑定多次,在VUE中,当组件销毁时需要调用 removeEventListener 移除监听\
  • 还可以使用另一种监听写法,则不会重复绑定监听事件
window.onmessage=function(event){
   var origin = event.origin
   if (origin !== "http://example.org:8080")
     return;
   // ...
}

event 的属性有:

  • data: 从其他 window 传递过来的数据
  • origin: 调用 postMessage 时,消息发送窗口的 origin。例如:“example.com:8080”。
  • source: 对发送消息的窗口对象的引用。可以使用此来在具有不同 origin 的两个窗口之间建立双向数据通信。

使用

通过window.open打开的页面

  • 父传子
// 父页面
const targetWindow = window.open('http://www.bbb.com');
setTimeout(()=>{
     targetWindow.postMessage('父传子', 'http://www.bbb.com')
}, 3000)
 
// 子页面
window.addEventListener('message', (e) => {
     console.log(e.data)
})

  • 子传父
//父页面
window.open('http://www.bbb.com');
window.addEventListener('message', (e) => {
     console.log(e.data)
})

//子页面
window.opener.postMessage('子传父', 'http://www.aaa.com')

两个窗口之间建立双向数据通信

/**
* localhost:10002/index页面
**/
// 接收消息
window.addEventListener('message', (e) => {
     console.log(e.data)
})
// 发送消息
const targetWindow = window.open('http://localhost:10001/user');
setTimeout(()=>{
     targetWindow.postMessage('来自10002的消息', 'http://localhost:10001')
}, 3000)
/**
* localhost:10001/user页面
**/
window.addEventListener('message', (e) => {
     console.log(e.data)
     if (event.origin !== "http://localhost:10002") 
     return;
     e.source.postMessage('来自10001的消息', e.origin)
})

页面与嵌套的 iframe 消息传递
http://www.domain1.com/a.html

<iframe id="iframe" src="http://www.domain2.com/b.html"></iframe>
 
<script>
var iframe = document.getElementById('iframe');
 
iframe.onload = function() {
   // 向domain2发送跨域数据
   iframe.contentWindow.postMessage('来自domain1的消息', 'http://www.domain2.com');
};
 
// 接受domain2返回数据
window.addEventListener('message',(e) => {
    console.log(e.data);
}, false);
</script>

http://www.domain2.com/b.html

<script>
// 接收domain1的数据
window.addEventListener('message',(e) => {
    console.log(e.data);
 
    if(e.origin !== 'http://www.domain1.com')
    return;
 
    // 发送消息给domain1
    window.parent.postMessage('来自domain2的消息', e.origin);
}, false);
</script>

安卓平台差异化处理

/* Android 平台 Post Message 消息监听 Hook */
window.Android_handleMessage = message => {
    // Android 使用 Base64 编码格式,需要先解码
    let data = decodeURIComponent(escape(window.atob(message)));
};

安全问题

  • 如果你不希望从其他网站接收 message,请不要为 message 事件添加任何事件监听器。
  • 如果你确实希望从其他网站接收message,请始终使用 origin 和 source 属性验证发件人的身份。
  • 当你使用 postMessage 将数据发送到其他窗口时,始终指定精确的目标 origin,而不是 *。