JS `BroadcastChannel` `MessageChannel` `SharedWorker` 的多层通信拓扑

各位靓仔靓女,早上好!我是你们今天的讲师,咱们今天的主题是:JS BroadcastChannelMessageChannelSharedWorker 的多层通信拓扑。

这玩意儿听起来是不是有点儿绕?别怕,咱们一步一步来,把这些概念掰开了揉碎了,保证你们听完之后,感觉就像刚吃了一顿麻辣火锅,浑身舒坦。

第一部分:单打独斗,各自为战

首先,咱们先来认识一下这三位“选手”,看看他们各自的特点和擅长什么。

  • BroadcastChannel:广播小喇叭

    BroadcastChannel 就像一个广播电台,一个页面发消息,所有监听同一个 channel name 的页面都能收到。注意,是所有!不管你是谁,只要你订阅了这个频道,就能听到。

    代码示例:

    // 页面 A (发送消息)
    const broadcastChannel = new BroadcastChannel('my-channel');
    broadcastChannel.postMessage('Hello from page A!');
    
    // 页面 B (接收消息)
    const broadcastChannel = new BroadcastChannel('my-channel');
    broadcastChannel.onmessage = (event) => {
      console.log('Received message:', event.data); // 输出: Hello from page A!
    };
    
    // 页面 C (也接收消息)
    const broadcastChannel = new BroadcastChannel('my-channel');
    broadcastChannel.onmessage = (event) => {
      console.log('Received message:', event.data); // 输出: Hello from page A!
    };

    优点: 简单粗暴,易于使用。适用于页面之间简单的广播通知。
    缺点: 只能在同源的浏览器上下文中通信。没有精确控制消息接收者的能力。
    适用场景: 页面之间的简单通知,例如:用户登录状态改变,主题切换等。

  • MessageChannel:点对点专线

    MessageChannel 就像两个页面之间建立了一条私密的电话线。必须通过 port1port2 分别发送和接收消息,并且只能在这两个端口之间通信。

    代码示例:

    // 页面 A
    const messageChannel = new MessageChannel();
    const port1 = messageChannel.port1;
    const port2 = messageChannel.port2;
    
    port1.onmessage = (event) => {
      console.log('Page A received:', event.data); // 输出: Page A received: Hello from page B!
    };
    
    // 将 port2 传递给页面 B (例如通过 postMessage)
    window.open('pageB.html', '_blank').onload = () => {
      window.open('pageB.html', '_blank').postMessage(port2, '*'); // 安全起见,请使用具体的 origin
    };
    
    // 页面 B (pageB.html)
    window.addEventListener('message', (event) => {
      if (event.data instanceof MessagePort) {
        const port2 = event.data;
        port2.onmessage = (event) => {
          console.log('Page B received:', event.data); // 输出: Page B received: Hello from page A!
        };
    
        port2.postMessage('Hello from page B!');
        port2.start(); // 必须调用 start() 才能开始接收消息
      }
    });
    
    port1.start(); // 必须调用 start() 才能开始接收消息

    优点: 安全性高,点对点通信,可以传递复杂的数据类型(例如:Transferable 对象)。
    缺点: 需要手动传递端口,比较繁琐。
    适用场景: 需要安全、可靠的点对点通信,例如:与 iframe 通信,传递大文件等。

  • SharedWorker:共享的后台劳模

    SharedWorker 就像一个常驻后台的工人,多个页面可以共享同一个 SharedWorker 实例,并通过它进行通信。

    代码示例:

    // worker.js
    self.addEventListener('connect', (event) => {
      const port = event.ports[0];
    
      port.onmessage = (event) => {
        console.log('Worker received:', event.data);
        port.postMessage('Hello from worker!');
      };
    
      port.start(); // 必须调用 start() 才能开始接收消息
    });
    
    // 页面 A
    const sharedWorker = new SharedWorker('worker.js');
    const portA = sharedWorker.port;
    
    portA.onmessage = (event) => {
      console.log('Page A received:', event.data); // 输出: Page A received: Hello from worker!
    };
    
    portA.postMessage('Hello from page A!');
    portA.start(); // 必须调用 start() 才能开始接收消息
    
    // 页面 B
    const sharedWorkerB = new SharedWorker('worker.js');
    const portB = sharedWorkerB.port;
    
    portB.onmessage = (event) => {
      console.log('Page B received:', event.data); // 输出: Page B received: Hello from worker!
    };
    
    portB.postMessage('Hello from page B!');
    portB.start(); // 必须调用 start() 才能开始接收消息

    优点: 可以跨页面共享数据和逻辑,减轻主线程负担。
    缺点: 需要处理并发问题,调试比较困难。
    适用场景: 需要在多个页面之间共享数据和逻辑,例如:缓存数据,执行计算密集型任务等。

第二部分:组团出道,强强联合

了解了这三个“选手”的特点,接下来咱们来看看如何将它们组合起来,构建更复杂的通信拓扑。

  • BroadcastChannel + SharedWorker:广播式后台服务

    SharedWorker 监听 BroadcastChannel,当收到消息时,再将消息转发给所有连接到 SharedWorker 的页面。

    代码示例:

    // worker.js
    const broadcastChannel = new BroadcastChannel('my-channel');
    
    broadcastChannel.onmessage = (event) => {
      console.log('Worker received broadcast:', event.data);
      // 将消息转发给所有连接的页面
      clients.matchAll().then(clients => {
        clients.forEach(client => {
          client.postMessage(event.data);
        });
      });
    };
    
    self.addEventListener('connect', (event) => {
      const port = event.ports[0];
    
      port.onmessage = (event) => {
        console.log('Worker received:', event.data);
      };
    
      port.start();
    });
    
    // 页面 A
    const sharedWorker = new SharedWorker('worker.js');
    const portA = sharedWorker.port;
    
    portA.onmessage = (event) => {
      console.log('Page A received from worker:', event.data);
    };
    
    portA.start();
    
    // 页面 B
    const sharedWorkerB = new SharedWorker('worker.js');
    const portB = sharedWorkerB.port;
    
    portB.onmessage = (event) => {
      console.log('Page B received from worker:', event.data);
    };
    
    portB.start();
    
    // 页面 C (发送广播消息)
    const broadcastChannelC = new BroadcastChannel('my-channel');
    broadcastChannelC.postMessage('Hello from page C!');

    解释:

    1. 页面 C 使用 BroadcastChannel 发送消息。
    2. SharedWorker 监听 BroadcastChannel,接收到消息后。
    3. SharedWorker 将消息转发给所有连接到它的页面(页面 A 和 页面 B)。

    优点: 页面 C 无需知道哪些页面连接到 SharedWorker,就可以将消息广播给它们。
    缺点: 所有连接到 SharedWorker 的页面都会收到消息,无法精确控制接收者。
    适用场景: 需要将消息广播给所有连接到后台服务的页面,例如:更新缓存数据,推送实时通知等。

  • MessageChannel + SharedWorker:点对点后台通信

    通过 MessageChannel 将端口传递给 SharedWorker,然后通过该端口与 SharedWorker 进行点对点通信。

    代码示例:

    // worker.js
    self.addEventListener('connect', (event) => {
      const port = event.ports[0];
    
      port.onmessage = (event) => {
        if (event.data.type === 'port') {
          const clientPort = event.data.port;
          clientPort.onmessage = (e) => {
            console.log('Worker received from client:', e.data);
            clientPort.postMessage('Hello from worker!');
          };
          clientPort.start();
        } else {
          console.log('Worker received:', event.data);
          port.postMessage('Hello from worker!');
        }
      };
    
      port.start();
    });
    
    // 页面 A
    const sharedWorker = new SharedWorker('worker.js');
    const portA = sharedWorker.port;
    
    const messageChannel = new MessageChannel();
    const clientPortA = messageChannel.port1;
    const workerPort = messageChannel.port2;
    
    clientPortA.onmessage = (event) => {
      console.log('Page A received from worker:', event.data); // 输出: Page A received from worker: Hello from worker!
    };
    
    portA.postMessage({ type: 'port', port: workerPort }, [workerPort]); // Transferable对象
    clientPortA.start();
    portA.start();
    clientPortA.postMessage('Hello from page A!');
    
    // 页面 B
    const sharedWorkerB = new SharedWorker('worker.js');
    const portB = sharedWorkerB.port;
    
    const messageChannelB = new MessageChannel();
    const clientPortB = messageChannelB.port1;
    const workerPortB = messageChannelB.port2;
    
    clientPortB.onmessage = (event) => {
      console.log('Page B received from worker:', event.data); // 输出: Page B received from worker: Hello from worker!
    };
    
    portB.postMessage({ type: 'port', port: workerPortB }, [workerPortB]); // Transferable对象
    clientPortB.start();
    portB.start();
    clientPortB.postMessage('Hello from page B!');

    解释:

    1. 页面 A 和 页面 B 分别创建 MessageChannel
    2. 页面 A 和 页面 B 将 port2 (workerPort) 通过 SharedWorker 传递给 worker.js
    3. worker.js 接收到 port2 后,就可以通过它与对应的页面进行点对点通信。

    优点: 可以实现页面与 SharedWorker 之间的安全、可靠的点对点通信。
    缺点: 需要手动传递端口,比较繁琐。
    适用场景: 需要页面与后台服务进行私密通信,例如:传递用户敏感信息,执行特定用户的任务等。

  • BroadcastChannel + MessageChannel + SharedWorker:三剑客合璧

    将三者结合起来,构建更复杂的通信拓扑,实现更灵活的通信方式。例如,使用 BroadcastChannel 广播通知,然后使用 MessageChannelSharedWorker 进行点对点通信,执行具体的操作。

    代码示例:

    // worker.js
    const broadcastChannel = new BroadcastChannel('my-channel');
    
    broadcastChannel.onmessage = (event) => {
      console.log('Worker received broadcast:', event.data);
      if (event.data.type === 'request') {
          clients.matchAll().then(clients => {
              clients.forEach(client => {
                // 找到发送请求的页面,通过 postMessage 将 event.data.port 传递过去
                  if (client.id === event.data.clientId) {
                      client.postMessage({type: 'port', port: event.data.port}, [event.data.port]);
                  }
              });
          });
      }
    };
    
    self.addEventListener('connect', (event) => {
      const port = event.ports[0];
    
      port.onmessage = (event) => {
        console.log('Worker received:', event.data);
      };
    
      port.start();
    });
    
    // 页面 A
    const broadcastChannelA = new BroadcastChannel('my-channel');
    const sharedWorker = new SharedWorker('worker.js');
    const portA = sharedWorker.port;
    
    portA.start();
    broadcastChannelA.postMessage({ message: 'Hello from page A!' });
    
    // 页面 B
    const broadcastChannelB = new BroadcastChannel('my-channel');
    const sharedWorkerB = new SharedWorker('worker.js');
    const portB = sharedWorkerB.port;
    
    portB.start();
    
    const messageChannel = new MessageChannel();
    const clientPort = messageChannel.port1;
    const workerPort = messageChannel.port2;
    
    clientPort.onmessage = (event) => {
        console.log('Page B received from worker:', event.data);
    };
    
    broadcastChannelB.postMessage({type: 'request', clientId: self.window.name || 'defaultClientId', port: workerPort}, [workerPort]);
    clientPort.start();

    解释:

    1. 页面 A 发送 BroadcastChannel 消息。
    2. SharedWorker 接收到消息。
    3. 页面 B 通过 BroadcastChannel 发送请求,携带 MessageChannelport2
    4. SharedWorkerport2 传递给 页面 B
    5. 页面 B 建立与 SharedWorker 的点对点连接。

    优点: 灵活性高,可以根据实际需求选择合适的通信方式。
    缺点: 实现复杂度较高,需要仔细设计通信流程。
    适用场景: 需要构建复杂的分布式应用,例如:实时协作系统,多页面应用等。

第三部分:注意事项,安全第一

在使用这些通信方式时,需要注意一些安全问题:

  • 同源策略: BroadcastChannelSharedWorker 都受到同源策略的限制。
  • 跨域通信: 如果需要跨域通信,可以使用 postMessage,但需要验证 origin
  • 数据安全: 不要通过 BroadcastChannel 传递敏感数据,因为任何订阅该频道的页面都可以接收到消息。
  • 并发控制:SharedWorker 中需要注意并发问题,可以使用锁或者其他机制来保证数据一致性。

第四部分:总结与展望

技术 特点 适用场景
BroadcastChannel 简单易用,广播通信 页面之间的简单通知,例如:用户登录状态改变,主题切换等。
MessageChannel 安全可靠,点对点通信,可以传递复杂的数据类型 需要安全、可靠的点对点通信,例如:与 iframe 通信,传递大文件等。
SharedWorker 跨页面共享数据和逻辑,减轻主线程负担 需要在多个页面之间共享数据和逻辑,例如:缓存数据,执行计算密集型任务等。
组合使用 灵活性高,可以根据实际需求选择合适的通信方式 需要构建复杂的分布式应用,例如:实时协作系统,多页面应用等。

总而言之,BroadcastChannelMessageChannelSharedWorker 都是 Web 开发中非常有用的工具,可以帮助我们构建更强大的应用。只要掌握了它们各自的特点,并合理地组合使用,就能创造出无限可能。

当然,Web 技术的进步日新月异,未来肯定会出现更多更强大的通信方式。让我们一起努力学习,不断探索,共同推动 Web 技术的发展!

好了,今天的讲座就到这里,希望对大家有所帮助。如果有什么疑问,欢迎随时提问!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注