前端如何实现 js 中在两个浏览器tab 中如何实现实时通信呢?

意思就是想要两个 tab页同时能获取到一个状态的更改。比如在 A 页面改的状态,在 B 页面也可以同时获取到

阅读 6.9k
10 个回答

共享SessionStorage

<script>
    ; (function () {
      if (!sessionStorage.length) {
        // 这个调用能触发目标事件,从而达到共享数据的目的
        localStorage.setItem('getSessionStorage', Date.now())
      }
      // 该事件是核心
      window.addEventListener('storage', function (event) {
        if (event.key == 'getSessionStorage') {
          // 已存在的标签页会收到这个事件
          localStorage.setItem('sessionStorage', JSON.stringify(sessionStorage))
          localStorage.removeItem('sessionStorage')
        } else if (event.key == 'sessionStorage' && !sessionStorage.length) {
          // 新开启的标签页会收到这个事件
          var data = JSON.parse(event.newValue),
            value
          for (key in data) {
            sessionStorage.setItem(key, data[key])
          }
        }
      })
    })()
  </script>

如果是同源的话,走 service worker 即可,很适合通知类应用场景。

上文有提到两种同源的方式

  • store 这个 LocalStore、SessionStore的更新事件
  • service worker 这个不了解,不过他说了应该是有道理的,

下面我在补充一个API
window.postMessage
window.postMessage - MDN 先贴下文档

window.postMessage() 方法可以安全地实现跨源通信。
/*
 * A 窗口的域名是<http://example.com:8080>,以下是 A 窗口的 script 标签下的代码:
 */

var popup = window.open(...popup details...);

// 如果弹出框没有被阻止且加载完成

// 这行语句没有发送信息出去,即使假设当前页面没有改变 location(因为 targetOrigin 设置不对)
popup.postMessage("The user is 'bob' and the password is 'secret'",
                  "https://secure.example.net");

// 假设当前页面没有改变 location,这条语句会成功添加 message 到发送队列中去(targetOrigin 设置对了)
popup.postMessage("hello there!", "http://example.com");

function receiveMessage(event)
{
  // 我们能相信信息的发送者吗?(也许这个发送者和我们最初打开的不是同一个页面).
  if (event.origin !== "http://example.com")
    return;

  // event.source 是我们通过 window.open 打开的弹出页面 popup
  // event.data 是 popup 发送给当前页面的消息 "hi there yourself!  the secret response is: rheeeeet!"
}
window.addEventListener("message", receiveMessage, false);
/*
 * 弹出页 popup 域名是 http://example.com,以下是 script 标签中的代码:
 */

//当 A 页面 postMessage 被调用后,这个 function 被 addEventListener 调用
function receiveMessage(event) {
  // 我们能信任信息来源吗?
  if (event.origin !== "http://example.com:8080") return;

  // event.source 就当前弹出页的来源页面
  // event.data 是 "hello there!"

  // 假设你已经验证了所受到信息的 origin (任何时候你都应该这样做), 一个很方便的方式就是把 event.source
  // 作为回信的对象,并且把 event.origin 作为 targetOrigin
  event.source.postMessage(
    "hi there yourself!  the secret response " + "is: rheeeeet!",
    event.origin,
  );
}

window.addEventListener("message", receiveMessage, false);

好了贴完了

  1. BroadcastChannel:BroadcastChannel 是一个新的浏览器 API,可以在不同的浏览器 tab 中进行实时通信。它类似于一个广播通道,可以将消息发送给所有订阅该频道的 tab。不过需要注意的是,BroadcastChannel API 目前不是所有浏览器都支持。

    ////  发送端
    
    // 创建 BroadcastChannel
    const channel = new BroadcastChannel('my-channel');
    
    // 监听 channel 的消息事件
    channel.addEventListener('message', (event) => {
      console.log('Received message:', event.data);
    });
    
    // 向 channel 发送消息
    channel.postMessage('Hello from main thread!');
    
    
    //// 接收端
    // 创建 BroadcastChannel
    const channel = new BroadcastChannel('my-channel');
    
    // 监听 channel 的消息事件
    channel.addEventListener('message', (event) => {
      console.log('Received message:', event.data);
    });
    
    // 向 channel 发送消息
    channel.postMessage('Hello from another tab!');
  2. localStorage:可以使用 localStorage 存储数据,在另外一个 tab 中监听 localStorage 的变化,从而实现实时通信。
  3. SharedWorker:SharedWorker 是一种可以在多个浏览器 tab 中共享的 JavaScript 运行环境。可以在 SharedWorker 中监听消息事件,从而实现浏览器 tab 之间的实时通信。

    ////  发送端
    
    // 创建 SharedWorker
    const worker = new SharedWorker('worker.js');
    
    // 监听 worker 的连接事件
    worker.port.addEventListener('connect', (e) => {
      console.log('Worker connected');
    
      // 监听 worker 的消息事件
      e.source.addEventListener('message', (event) => {
     console.log('Received message:', event.data);
      });
    });
    
    // 向 worker 发送消息
    worker.port.postMessage('Hello from main thread!');
    
    
    //// 接收端:
    
    // 监听端口对象的消息事件
    self.addEventListener('message', (event) => {
      console.log('Received message:', event.data);
    
      // 向所有连接的端口发送消息
      self.clients.matchAll().then((clients) => {
     clients.forEach((client) => {
       client.postMessage(event.data);
     });
      });
    });
    

以前遇到过这样的问题,用户可能打开了多个标签,其中有个标签是一个列表页面,如果在另外的页面中修改了数据,则希望这个打开的列表页面能够及时刷新,后面是通过BroadcastChannel这个实现的。

从设计角度看,
所有的功能都应该在同一个 Tab,
不讨论跨 Tab 的功能实现

毕竟有 iframe 为啥一定要实现多 Tab 的通信?
有啥实际的业务场景吗?
可以直接喷需求,
改成单页面的需求,
用 iframe 降级成稳定的单页面通信

想想论坛和聊天室就知道,
找后端开一个 WebScoket 岂不美哉?
没理由一定要前端康所有,
合理的诉求应该提出来

推荐问题
宣传栏