京东一面:postMessage 如何区分不同类型的消息 🤪🤪🤪

面试导航 是一个专注于前、后端技术学习和面试准备的 免费 学习平台,提供系统化的技术栈学习,深入讲解每个知识点的核心原理,帮助开发者构建全面的技术体系。平台还收录了大量真实的校招与社招面经,帮助你快速掌握面试技巧,提升求职竞争力。如果你想加入我们的交流群,欢迎通过微信联系:yunmz777

在现代 Web 开发中,跨上下文通信是一个常见需求。无论是与 iframe 通信,还是与 Web Worker 交换数据,postMessage API 都提供了安全可靠的跨域通信机制。本文将深入探讨如何识别和区分不同来源的消息,包括 iframe 与 Web Worker 的消息区分,以及如何处理来自多个 Web Worker 的消息。

postMessage 简介

postMessage 是 HTML5 引入的一种安全的跨源通信方法,允许来自不同源的脚本通过指定的接口传递消息。这种通信方式广泛应用于:

  1. 主页面与 iframe 之间的通信

  2. 不同窗口间的通信(如通过 window.open 打开的窗口)

  3. 主线程与 Web Worker 之间的通信

它的基本用法如下:

js 复制代码
// 发送消息
targetWindow.postMessage(message, targetOrigin, [transfer]);

// 接收消息
window.addEventListener("message", (event) => {
  // 处理接收到的消息
  console.log(event.data);
});

区分 iframe 和 Web Worker 的消息

在实际应用中,我们经常需要区分消息来源,特别是同时使用 iframe 和 Web Worker 时,这两个通信机制虽然都使用了相似的 API,但它们有明显的特征差异。

特性 iframe 消息 Web Worker 消息
事件监听方式 window.addEventListener('message') worker.addEventListener('message')
event.source 指向发送消息的窗口对象 为 null
event.origin 表示发送消息的源(协议、域名和端口) 为空字符串 ""
通信对象 通过 window 对象 通过 Worker 实例

接下来我们将使用一个完整示例,通过最终的输出我们来区分 iframe 和 Web Worker 的消息:

主页面 index.html:

html 复制代码
<!DOCTYPE html>
<html>
  <head>
    <title>区分 postMessage 源(优化版)</title>
  </head>
  <body>
    <h1>消息源区分示例</h1>
    <iframe id="myIframe" src="iframe.html" width="400" height="100"></iframe>
    <div id="messages"></div>

    <script>
      // 创建一个 Web Worker
      const worker = new Worker("worker.js");
      const messagesDiv = document.getElementById("messages");

      // 监听来自 iframe 的消息
      window.addEventListener("message", function (event) {
        // 确保消息不是来自当前窗口
        if (event.source !== window) {
          // 检查消息来源是否是 iframe
          if (event.source) {
            messagesDiv.innerHTML += `<p>收到 iframe 消息: ${JSON.stringify(
              event.data
            )},origin: ${event.origin}</p>`;
            console.log("来自 iframe 的消息", event);
          }
        }
      });

      // 单独监听来自 Worker 的消息
      worker.addEventListener("message", function (event) {
        messagesDiv.innerHTML += `<p>收到 Worker 消息: ${JSON.stringify(
          event.data
        )}</p>`;
        console.log("来自 Worker 的消息", event);
      });

      // 向 iframe 发送消息(只发送一次)
      setTimeout(() => {
        const iframe = document.getElementById("myIframe");
        if (iframe.contentWindow) {
          iframe.contentWindow.postMessage(
            {
              type: "fromMain",
              message: "来自主页面的消息",
            },
            "*"
          );
        }
      }, 1000);

      // 向 Worker 发送消息(只发送一次)
      setTimeout(() => {
        worker.postMessage({
          type: "fromMain",
          message: "主页面发给 Worker 的消息",
        });
      }, 2000);
    </script>
  </body>
</html>

iframe 页面 (iframe.html):

html 复制代码
<!DOCTYPE html>
<html>
  <head>
    <title>iframe 页面</title>
  </head>
  <body>
    <h2>iframe 内容</h2>
    <div id="message"></div>
    <script>
      // 监听来自父窗口的消息
      window.addEventListener("message", function (event) {
        // 确保消息来自父窗口
        if (event.source === window.parent) {
          const messageDiv = document.getElementById("message");
          messageDiv.innerHTML = `收到消息: ${JSON.stringify(event.data)}`;
          console.log("iframe 收到消息:", event.data);

          // 向父窗口回复消息(只回复一次)
          window.parent.postMessage(
            {
              type: "fromIframe",
              message: "来自 iframe 的回复",
            },
            "*"
          );
        }
      });
    </script>
  </body>
</html>

Web Worker (worker.js):

js 复制代码
// 监听来自主线程的消息
self.addEventListener("message", function (event) {
  console.log("Worker 收到消息:", event.data);

  // 向主线程回复消息(只回复一次)
  self.postMessage({
    type: "fromWorker",
    message: "来自 Worker 的回复",
  });
});

在上面的这些代码中,我们可以看到,它正是通过 event.source 来区分,如果 event.source 不为 null,则消息来自 iframe 或其他窗口,如果 event.source 为 null,可能是来自 Worker 的消息。

最终输出结果如下图所示:

区分不同 Web Worker 的消息

当应用中使用多个 Web Worker 时,我们需要区分消息来源于哪个具体的 Worker。下面介绍几种有效的方法:

  1. 分别添加事件监听器:为每个 Worker 添加单独的事件处理程序

  2. 使用消息标识:在消息对象中添加特定的标识字段

  3. 利用 event.target:通过比较 event.target 和已保存的 Worker 引用确定来源

如下代码所示:

html 复制代码
<!DOCTYPE html>
<html>
  <head>
    <title>区分多个 Worker 消息</title>
  </head>
  <body>
    <h1>多 Worker 消息区分示例</h1>
    <button id="worker1Btn">发送消息给 Worker 1</button>
    <button id="worker2Btn">发送消息给 Worker 2</button>
    <div id="messages"></div>

    <script>
      const messagesDiv = document.getElementById("messages");

      // 方法1: 为每个 Worker 分别添加事件监听器
      const worker1 = new Worker("worker1.js");
      const worker2 = new Worker("worker2.js");

      worker1.addEventListener("message", function (event) {
        messagesDiv.innerHTML += `<p>Worker 1 回复: ${event.data.message} (ID: ${event.data.workerId})</p>`;
      });

      worker2.addEventListener("message", function (event) {
        messagesDiv.innerHTML += `<p>Worker 2 回复: ${event.data.message} (ID: ${event.data.workerId})</p>`;
      });

      // 方法2: 使用统一的处理函数,通过消息ID区分
      const worker3 = new Worker("worker3.js");
      const worker4 = new Worker("worker4.js");

      function handleWorkerMessage(event) {
        const data = event.data;

        if (data.workerId === "worker3") {
          messagesDiv.innerHTML += `<p>Worker 3 回复: ${data.message} (ID: ${data.workerId})</p>`;
        } else if (data.workerId === "worker4") {
          messagesDiv.innerHTML += `<p>Worker 4 回复: ${data.message} (ID: ${data.workerId})</p>`;
        }
      }

      worker3.addEventListener("message", handleWorkerMessage);
      worker4.addEventListener("message", handleWorkerMessage);

      // 方法3: 通过 event.target 区分
      function identifyWorkerByTarget(event) {
        if (event.target === worker3) {
          messagesDiv.innerHTML += `<p>确认是 Worker 3 通过 event.target 判断</p>`;
        } else if (event.target === worker4) {
          messagesDiv.innerHTML += `<p>确认是 Worker 4 通过 event.target 判断</p>`;
        }
      }

      worker3.addEventListener("message", identifyWorkerByTarget);
      worker4.addEventListener("message", identifyWorkerByTarget);

      // 添加按钮事件
      document
        .getElementById("worker1Btn")
        .addEventListener("click", function () {
          worker1.postMessage({ command: "doTask", data: "Task for Worker 1" });
        });

      document
        .getElementById("worker2Btn")
        .addEventListener("click", function () {
          worker2.postMessage({ command: "doTask", data: "Task for Worker 2" });
        });

      // 启动时自动发送消息给所有 Worker
      setTimeout(() => {
        worker1.postMessage({ command: "identify" });
        worker2.postMessage({ command: "identify" });
        worker3.postMessage({ command: "identify" });
        worker4.postMessage({ command: "identify" });
      }, 1000);
    </script>
  </body>
</html>

总结

postMessage API 提供了安全的跨上下文通信能力,可用于 iframe 和 Web Worker 场景。区分 iframe 和 Worker 消息可通过 event.source 属性(iframe 有值,Worker 为 null)和不同的事件监听方式实现。对于多个 Web Worker 的区分,可采用分别添加事件监听器、在消息中添加标识字段或利用 event.target 与 Worker 引用比较等方法。使用结构化消息格式并验证消息来源可确保安全和准确的跨上下文通信。

相关推荐
优雅的落幕2 小时前
前端---初识HTML(前端三剑客)
java·前端·javascript·css·html
nujnewnehc2 小时前
vue 少了2道面试题, vapor 来了后 vnode 和 diff 算法可以不需要了?
前端·vue.js·vapor
codingandsleeping3 小时前
前端工程化之webpack(万字)
前端·javascript
Jiude3 小时前
UnoCSS presetWind4() 背景色使用 color-mix() 的原因及解决方案
前端·css
无名之逆4 小时前
Hyperlane:Rust 生态中的轻量级高性能 HTTP 服务器库,助力现代 Web 开发
服务器·开发语言·前端·后端·http·面试·rust
范哥来了4 小时前
python web开发django库安装与使用
前端·python·django
烛阴4 小时前
JavaScript 的 “new Function”:你不知道的黑魔法,让代码更灵活!
前端·javascript
ConardLi4 小时前
发布第五天,我的开源项目突破 1.7 K Star!
前端·javascript·人工智能
鱼樱前端5 小时前
🔥 Vue2 vs Vue3 的 h 函数终极指南:从入门到源码级深度解析
前端·vue.js