如何实现浏览器内多个标签页之间的通信?

97 阅读3分钟

浏览器跨标签页通信

大家都知道,浏览器可以同时打开多个标签页的,而且在实际的使用场景中,打开多个标签页是非常常见的情况。比如说某个网站的列表页面,点击之后新开标签页展示详情,用户在详情里面进行了操作,列表标签页也需要更新数据。总的来说:多个标签页之间通信是很有必要的。

  • Broadcast Channel(广播频道)

    用于同源(相同协议、主机和端口号)浏览器的多个标签页之间广播通信,每个 Broadca Channel 实例都属于一个特定的通道名称,并且可以由具有该名称的页面监听。(同源:指该方法无法跨域通信。)

  • localStorage

    是同源(相同协议、主机和端口号)浏览器的多个标签页共享数据的存储空间,可以实现多标签之间的通信。(session 是会话级的存储空间,每个标签页都是单独的。)

a.html

function setStorage(){  
   if(!localStorage.getItem('hasStorage')){  
       window.open('/b.html');
       localStorage.setItem('hasStorage',1);
   }  
   setTimeout(()=>{  
       localStorage.setItem('man','摸鱼君');  
   },3000);  
}
b.html

window.onunload = () =>{  
    localStorage.removeItem('hasStorage');  
    localStorage.removeItem('man');  
}  
const app = document.getElementById('app');  
window.addEventListener('stroage', e=>{  
    const app = document.getElementById('app');  
    app.innerText = e.newValue;  
})
  • SharedWorker

    是WebWorker 的一种,允许同源(相同协议、主机和端口号)浏览器的多个标签页共享同一个全局上下文(比如多个 windows,iframes 和 workers)。即可以用来在多个标签页之间共享数据,一个标签页对数据进行修改,其他标签页可以立即看到更新。但必须保证这些标签页都是同源的。

// sharedWorker.js

const set = new Set();  
onconnect = event => {  
   const port = event.ports[0];  
   set.add(port);  
  
   // 接收信息  
   port.onmessage = e => {  
       // 广播信息  
       set.forEach(p => {  
           p.postMessage(e.data);  
       })  
   }  
  
   // 发送信息  
   port.postMessage("SharedWorker发出信息");  
}
// A.html

<script>  
    const sharedWorker = new SharedWorker('./sharedWorker.js');  
    sharedWorker.port.onmessage = e => {  
        console.info("A收到:", e.data);  
    }  
</script>
<script>  
// B.html
    const sharedWorker = new SharedWorker('./sharedWorker.js');  
    let btnB = document.getElementById("btnB");  
    btnB.addEventListener("click", () => {  
        sharedWorker.port.postMessage("B发出消息");  
    })  
</script>

以上代码是最简单的 A、B 两个页面发送消息的实现demo。SharedWorker 初始化完成后:A 页面首先收到“A收到:SharedWorker发出信息”,然后 B 页面点击按钮,发送消息给 A页面,A 页面收到“A收到:B发出消息”。

  • WebSocket

    是一种全双工(full-duplex)通信协议,允许客户端和服务器之间实时通信,享受平等关系(比如说聊天工具)。如果两个标签页都与服务器建立了 WebSocker 连接,则可以直接进行实时通信。

  • postMessage

    两个需要交互的 tab 页面具有依赖关系。如 A 页面中通过 JavaScript 的 window.open 打开 B 页面,或者 B 页面通过 iframe 嵌入到 A 页面,这种情形最简单,可以通过 HTML5 的 window.postMessage API 完成通信,用于 postMessage 函数是绑定在 window 全局对象下,因此通信的页面中必须有一个页面(如 A 页面)可以获取另一个页面(如 B 页面)的 window 对象,这样才可以完成单向通信;B 页面无需获取 A 页面的 window 对象,如果需要 B 页面对 A 页面的通信,只需要在 B 页面侦听 message 事件,获取事件中传递的 source 对象,该对象即为 A 页面 window 对象的引用:

// B页面
window.addEventLister("message", (e) => {
  let { data, source, origin } = e;
  source.postMessage("message echo", "/");
});

postMessage 的第一个参数为消息实体,它是一个结构化对象,即可以通过JSON.stringify 和 JSON.parse函数还原的对象;第二个参数为消息发送范围选择器,设置为"/"意味着只发送消息给同源的页面,设置"*"则发送全部页面。

  • 定时器 setInterval+cookie

    在页面 A 设置一个使用 setInterval 定时器不断刷新,检查 cookies 的值是否发生变化,如果变化就进行刷新的操作

    由于 cookies 是在同域可读的,所以在页面 B 审核的时候改变 Cookies 的值,页面 A 自然是可以拿到的

    这样做确实可以实现想要的功能,但是这样方式相当浪费资源。虽然在这个性能过盛的时代,浪费不浪费也感觉不出来,但是这种实现方案,确实不够优雅。