[js] 跨窗口(标签页)通信方法

616 阅读2分钟

一、前言

最近需求有跨标签页操作,借此总结跨窗口(标签页)通信的常用方法,包括同源和跨域两种情况,给出的示例代码能让你快速上手。

二、同源

localStorage 配合 storage 事件:简单、兼容性好

window.addEventListener('storage', (e) => {
    // 关键属性:key、oldValue、newValue、url
    if (e.key != 'wc') return;
    const data = JSON.parse(e.newValue);
    console.log(data);
});

const data = {
    a: 111,
    now: Date.now(), // 保证数据变更,从而一定触发storage事件
};
localStorage.setItem('wc', JSON.stringify(data));

Broadcast Channel API:功能全

// 创建广播频道,也是“订阅”
const channel = new BroadcastChannel('test_channel');
// 接收频道的所有消息
channel.addEventListener('message', (e) => {
    console.log(e.data);
});
// 消息无法反序列化时触发
channel.addEventListener('messageerror', (e) => {
    console.error(e);
});
// 发送消息
channel.postMessage({
    url: location.href,
    data: '123',
});
// 断开频道连接
// channel.close();

三、跨域

利用 iframe 的 postmessage 接口

parent.html(注意修改子页面的 iframe 地址)

<!DOCTYPE html>
<html>
<head>
  <meta charset='UTF-8'>
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Document</title>
  <style>
    #theIframe {
      width: 450px;
      height: 200px;
    }
  </style>
</head>
<body>
  <h1>Parent</h1>
  <button id="sendBtn">发送消息给子页面</button>
  <p>收到消息:<span id="msg"></span></p>
  <!-- 注意:修改为子页面地址 -->
  <iframe src="http://localhost:7002/iframe/child.html" id="theIframe"></iframe>

  <script>
    // 子页面来源
    const IFRAME_SOURCE = 'http://localhost:7002';

    theIframe.addEventListener('load', () => {
      try {
        // 同源可以做任何事儿
        theIframe.contentDocument.body.prepend("Hello, world!");
      } catch (err) {
        console.log('iframe 跨域');
      }

      sendBtn.addEventListener('click', () => {
        // 发送消息到子页面
        theIframe.contentWindow.postMessage({ command: 'show-text', text: '1' }, IFRAME_SOURCE);
      });
    });

    // 接收来自子页面的消息
    window.addEventListener('message', (e) => {
      // 安全检查:忽略未知源的消息
      if (e.origin !== IFRAME_SOURCE) return;

      // 按消息类型做不同处理
      const { command, text } = e.data;
      switch (command) {
        case 'show-text':
          msg.textContent = text;
          break;
      }
    });
  </script>
</body>
</html>

child.html

<!DOCTYPE html>
<html>
<head>
  <meta charset='UTF-8'>
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Document</title>
</head>
<body>
  <h1>Child</h1>
  <button id="sendBtn">发送消息给父页面</button>
  <p>收到消息:<span id="msg"></span></p>

  <script>
    // 父页面来源
    const PARENT_SOURCE = 'http://localhost:7001';

    sendBtn.addEventListener('click', () => {
      // 发送消息到父页面
      window.parent.postMessage({ command: 'show-text', text: '3' }, PARENT_SOURCE);
    });

    // 接收来自父页面的消息并回应
    window.addEventListener('message', (e) => {
      if (e.origin !== PARENT_SOURCE) return;
      const { command, text } = e.data;
      switch (command) {
        case 'show-text':
          msg.textContent = text;
          break;
      }

      // 回应
      setTimeout(() => {
        e.source.postMessage({ command: 'show-text', text: '2' }, e.origin);
      }, 1000);
    });
  </script>
</body>
</html>

启动示例:

启动子页面服务:npx serve -l 7002
启动父页面服务:npx serve -l 7001
最后访问父页面即可。

三、结束

如果帮到你了,可以点个赞,欢迎交流沟通。