各位靓仔靓女,早上好!我是你们今天的讲师,咱们今天的主题是:JS BroadcastChannel
、MessageChannel
、SharedWorker
的多层通信拓扑。
这玩意儿听起来是不是有点儿绕?别怕,咱们一步一步来,把这些概念掰开了揉碎了,保证你们听完之后,感觉就像刚吃了一顿麻辣火锅,浑身舒坦。
第一部分:单打独斗,各自为战
首先,咱们先来认识一下这三位“选手”,看看他们各自的特点和擅长什么。
-
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
就像两个页面之间建立了一条私密的电话线。必须通过port1
和port2
分别发送和接收消息,并且只能在这两个端口之间通信。代码示例:
// 页面 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!');
解释:
- 页面 C 使用
BroadcastChannel
发送消息。 SharedWorker
监听BroadcastChannel
,接收到消息后。SharedWorker
将消息转发给所有连接到它的页面(页面 A 和 页面 B)。
优点: 页面 C 无需知道哪些页面连接到
SharedWorker
,就可以将消息广播给它们。
缺点: 所有连接到SharedWorker
的页面都会收到消息,无法精确控制接收者。
适用场景: 需要将消息广播给所有连接到后台服务的页面,例如:更新缓存数据,推送实时通知等。 - 页面 C 使用
-
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!');
解释:
- 页面 A 和 页面 B 分别创建
MessageChannel
。 - 页面 A 和 页面 B 将
port2
(workerPort) 通过SharedWorker
传递给worker.js
。 worker.js
接收到port2
后,就可以通过它与对应的页面进行点对点通信。
优点: 可以实现页面与
SharedWorker
之间的安全、可靠的点对点通信。
缺点: 需要手动传递端口,比较繁琐。
适用场景: 需要页面与后台服务进行私密通信,例如:传递用户敏感信息,执行特定用户的任务等。 - 页面 A 和 页面 B 分别创建
-
BroadcastChannel
+MessageChannel
+SharedWorker
:三剑客合璧将三者结合起来,构建更复杂的通信拓扑,实现更灵活的通信方式。例如,使用
BroadcastChannel
广播通知,然后使用MessageChannel
与SharedWorker
进行点对点通信,执行具体的操作。代码示例:
// 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();
解释:
- 页面 A 发送
BroadcastChannel
消息。 SharedWorker
接收到消息。- 页面 B 通过
BroadcastChannel
发送请求,携带MessageChannel
的port2
。 SharedWorker
将port2
传递给 页面 B- 页面 B 建立与
SharedWorker
的点对点连接。
优点: 灵活性高,可以根据实际需求选择合适的通信方式。
缺点: 实现复杂度较高,需要仔细设计通信流程。
适用场景: 需要构建复杂的分布式应用,例如:实时协作系统,多页面应用等。 - 页面 A 发送
第三部分:注意事项,安全第一
在使用这些通信方式时,需要注意一些安全问题:
- 同源策略:
BroadcastChannel
和SharedWorker
都受到同源策略的限制。 - 跨域通信: 如果需要跨域通信,可以使用
postMessage
,但需要验证origin
。 - 数据安全: 不要通过
BroadcastChannel
传递敏感数据,因为任何订阅该频道的页面都可以接收到消息。 - 并发控制: 在
SharedWorker
中需要注意并发问题,可以使用锁或者其他机制来保证数据一致性。
第四部分:总结与展望
技术 | 特点 | 适用场景 |
---|---|---|
BroadcastChannel |
简单易用,广播通信 | 页面之间的简单通知,例如:用户登录状态改变,主题切换等。 |
MessageChannel |
安全可靠,点对点通信,可以传递复杂的数据类型 | 需要安全、可靠的点对点通信,例如:与 iframe 通信,传递大文件等。 |
SharedWorker |
跨页面共享数据和逻辑,减轻主线程负担 | 需要在多个页面之间共享数据和逻辑,例如:缓存数据,执行计算密集型任务等。 |
组合使用 | 灵活性高,可以根据实际需求选择合适的通信方式 | 需要构建复杂的分布式应用,例如:实时协作系统,多页面应用等。 |
总而言之,BroadcastChannel
、MessageChannel
和 SharedWorker
都是 Web 开发中非常有用的工具,可以帮助我们构建更强大的应用。只要掌握了它们各自的特点,并合理地组合使用,就能创造出无限可能。
当然,Web 技术的进步日新月异,未来肯定会出现更多更强大的通信方式。让我们一起努力学习,不断探索,共同推动 Web 技术的发展!
好了,今天的讲座就到这里,希望对大家有所帮助。如果有什么疑问,欢迎随时提问!