SharedWorker 多页面相互通信示例详解
田八 人气:0正文
SharedWorker
是一个新的Web Worker API
,它允许你在多个页面之间共享一个Worker
。
SharedWorker
代表一种特定类型的Worker
,可以在多个浏览器上下文中运行,比如多个页面或者多个iframe
。
什么是 SharedWorker
根据前几篇的了解,大家应该对Worker
有了一定的了解,SharedWorker
是Worker
的一种,它允许你在多个页面之间共享一个Worker
。
SharedWorker
对比于Service Worker
要简单很多,同样也是继承与Worker
,但是它的生命周期是独立于页面的。
SharedWorker
和其他Worker
的相同,都是需要同源的,它是通过url
来区分是否是同一个SharedWorker
。
使用 SharedWorker
SharedWorker
的使用很简单,只需要在创建Worker
的时候,需要传入一个name
参数,这个name
参数就是SharedWorker
的标识,相同的name
会共享同一个SharedWorker
。
const sharedWorker = new SharedWorker('worker.js', 'mySharedWorker');
默认情况下name
并不是必须的,这个时候相同的url
会共享同一个SharedWorker
。
const sharedWorker = new SharedWorker('worker.js');
创建SharedWorker
之后,我们就可以通过port
属性来和SharedWorker
进行通信了。
sharedWorker.port.onmessage = (e) => { console.log(e.data); };
在主线程中,可以通过创建出来的worker
对象中的port
属性来和SharedWorker
进行通信。
使用worker.port.onmessage
来监听SharedWorker
的消息。
在SharedWorker
中,可以通过self.onconnect
来监听SharedWorker
的连接。
在监听事件中会获取到与SharedWorker
连接的port
,通过这个port
就可以和主线程进行通信了。
self.onconnect = (e) => { const port = e.ports[0]; port.postMessage('hello'); };
通信还是通过postMessage
来进行的。
通信
上面的通信只是一个简单的示例,在主线程中创建的SharedWorker
,并使用port
来和SharedWorker
进行通信。
通常情况下,我们会使用onmessage
来监听SharedWorker
的消息,使用这种方式默认是打开状态,如果使用addEventListener
来监听SharedWorker
的消息,需要手动调用start
方法来开启。
sharedWorker.port.addEventListener('message', (e) => { console.log(e.data); }); sharedWorker.port.start();
在主线程中,我们可以通过port
来实现和SharedWorker
的通信。
// 发送消息 sharedWorker.port.postMessage('hello'); // 监听消息 sharedWorker.port.onmessage = (e) => { console.log(e.data); }; // 或者 sharedWorker.port.addEventListener('message', (e) => { console.log(e.data); }); sharedWorker.port.start();
在SharedWorker
中使用方式也是一样的,不同的是,我们需要通过self.onconnect
来监听SharedWorker
的连接。
self.onconnect = (e) => { const port = e.ports[0]; // 发送消息 port.postMessage('hello'); // 监听消息 port.onmessage = (e) => { console.log(e.data); }; // 或者 port.addEventListener('message', (e) => { console.log(e.data); }); port.start(); };
可以看到上面监听的时候,获取port
是一个数组,这是因为SharedWorker
可以和多个页面进行通信。
每个页面都会有一个port
,通过这个port
和SharedWorker
进行通信。
通常情况下我们会记录下所有的port
,然后在需要的时候进行通信。
const ports = []; self.onconnect = (e) => { const port = e.ports[0]; ports.push(port); port.onmessage = (e) => { console.log(e.data); }; }; // 发送消息 function sendMessage(message) { ports.forEach((port) => { port.postMessage(message); }); }
上面我们的案例中,我们只创建了一个SharedWorker
,所以只有一个port
,但是如果我们创建了多个SharedWorker
,那么就会有多个port
。
这里收到消息之后浏览器控制台没有任何打印信息,因为SharedWorker
的作用域中没有window
对象,所以console
、alert
等方法都是无法使用的。
如果我们需要调试SharedWorker
,可以在浏览器地址栏中输入chrome://inspect/#workers
,这样就可以看到当前页面中的SharedWorker
。
指定页面通信
上面我们拿到了所有的port
,但是我们并不知道这个port
是哪个页面的,所以我们需要在创建SharedWorker
的时候,指定每个页面的标识。
但是很遗憾,SharedWorker
并没有提供这样的功能,所以我们需要自己实现。
这里我们可以使用双数组来实现,一个数组用来存储页面的标识,一个数组用来存储port
。
const pages = []; const ports = []; self.onconnect = (e) => { const port = e.ports[0]; ports.push(port); // 获取页面标识 const page = Math.random().toString(36).substr(2); port.postMessage(page); pages.push(page); // 监听消息 port.onmessage = (e) => { const index = ports.indexOf(port); const page = pages[index]; // 这样就可以知道是哪个页面发来的消息了 console.log(page); }; };
有了上面的代码,我们就可以知道是哪个页面发来的消息了,并且也可以指定页面发送消息。
实战
上面我们已经知道了如何使用SharedWorker
,那么我们就可以使用它来实现一个简单的聊天室。
这里我们使用SharedWorker
来实现消息的转发,页面之间的通信使用postMessage
。
- SharedWorker
const uuids = []; const ports = []; self.onconnect = (e) => { const port = e.ports[0]; ports.push(port); // 获取页面标识 const uuid = Math.random().toString(36).substr(2); uuids.push(uuid); port.postMessage({ type: 'connect', uuid: uuid }); // 监听消息 port.onmessage = (e) => { const index = ports.indexOf(port); const uuid = uuids[index]; // 群发消息 broadcast({ type: 'message', sender: uuid, message: e.data.message }); }; }; // 群发消息 const broadcast = (data) => { ports.forEach((port) => { port.postMessage(data); }); }
- index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .chat { width: 100%; height: 100%; display: flex; flex-direction: column; } #messages { height: 500px; width: 300px; overflow: auto; border: #3498db 1px solid; padding: 0 10px; } .chat__messages { flex: 1; overflow-y: auto; margin: 4px 0; } .sender { font-size: 12px; margin: 0; padding: 0; } .sender:after { content: ' :'; } .message { width: auto; max-width: 300px; background: #3498db; padding: 10px; border-radius: 10px; margin: 10px 0; font-size: 16px; color: #fff; } </style> </head> <body> <div class="chat"> <div id="messages"> </div> <div class="chat__input"> <input type="text" id="message"> <button id="send">发送</button> </div> </div> <script> let uuid = '' const sharedWorker = new SharedWorker('sharedWorker.js'); sharedWorker.port.onmessage = (e) => { if (e.data.type === 'connect') { uuid = e.data.uuid; alert('连接成功') return; } const messageDom = ` <div class="chat__messages"> <p class="sender" ${uuid === e.data.sender ? 'style="color: #165DFF"' : ""}>${e.data.sender}</p> <p class="message">${e.data.message}</p> </div> ` document.getElementById('messages').insertAdjacentHTML('beforeend', messageDom); // 滚动到底部 document.getElementById('messages').scrollTop = document.getElementById('messages').scrollHeight; }; document.getElementById('send').addEventListener('click', () => { const message = document.getElementById('message').value; if (!message) { alert('请输入内容') return; } const data = { type: 'message', message: message, } sharedWorker.port.postMessage(data); document.getElementById('message').value = ''; }); </script> </body> </html>
上面的代码我们实现了一个简单的聊天室,我们可以在多个页面打开聊天室,然后在其中一个页面发送消息,其他页面都会收到消息。
上面的代码中还有一个uuid
,=还可以基于这么来实现私聊,比如我们可以在页面中添加一个select
,然后选择一个用户,然后在发送消息的时候,将uuid
传递过去,然后在SharedWorker
中判断,如果是私聊,就只发送给指定的用户。
这一块就留给感兴趣的同学自己实现了。
SharedWorker 的关闭
上面我们实现了一个简单的聊天室,但是我们发现,当我们关闭页面的时候,SharedWorker
并没有关闭,这是因为SharedWorker
是一个长连接,当我们关闭页面的时候,SharedWorker
并没有关闭,所以我们需要在页面关闭的时候,手动关闭SharedWorker
。
// 关闭页面的时候,关闭 SharedWorker window.addEventListener('beforeunload', () => { sharedWorker.port.postMessage({ type: 'close', uuid: uuid }); }); // SharedWorker.js /** 省略其他代码 **/ onconnect = (e) => { const port = e.ports[0]; port.onmessage = (e) => { if (e.data.type === 'close') { const index = ports.indexOf(port); ports.splice(index, 1); return; } }; };
上面的代码中,我们在页面关闭的时候,发送一个close
的消息,然后在SharedWorker
中,将port
从ports
中移除,这样就可以关闭SharedWorker
了。
SharedWorker 的生命周期
SharedWorker
的生命周期没有那么复杂,不像ServiceWorker
有那么多的过程,它只有一条规则,就是当所有创建SharedWorker
的页面关闭之后,那么SharedWorker
的生命就走到了尽头,否则它就会一直常驻。
SharedWorker 还可以做什么?
聊天室只是一个小玩具,我们还可以做很多事情,我们最常见的就是跨页面登录,例如GitHub
,我们在一个页面登录,然后在其他页面就可以获取到登录信息,这就是SharedWorker
的一个应用场景。
还有一个应用场景就是,我们可以在一个页面中,监听一个WebSocket
,然后在其他页面中,发送消息,这样就可以实现跨页面的WebSocket
。
其实任何跨页面的通信,都可以使用SharedWorker
来实现,但是SharedWorker
的缺点也很明显,就是不支持IE
,所以如果你的项目需要兼容IE
,那么就不要使用SharedWorker
了。
优点很明确,就是可以跨页面通信,缺点也很明显,就是浏览器兼容确实有点捉急。
总结
本文主要介绍了SharedWorker
的基本使用,并使用SharedWorker
实现了一个简单的聊天室。
SharedWorker
的使用还是比较简单的,不同于Worker
那么单纯,SharedWorker
是一个长连接,所以我们可以在多个页面中,共享一个SharedWorker
,这样就可以实现跨页面通信了。
不同于ServiceWorker
有那么多的限制,SharedWorker
除了兼容性其实功能非常强大,且在实际项目中的应用场景也更加广泛。
加载全部内容