SharedWorker?多頁面相互通信示例詳解
正文
SharedWorker
是一個(gè)新的Web Worker API
,它允許你在多個(gè)頁面之間共享一個(gè)Worker
。
SharedWorker
代表一種特定類型的Worker
,可以在多個(gè)瀏覽器上下文中運(yùn)行,比如多個(gè)頁面或者多個(gè)iframe
。
什么是 SharedWorker
根據(jù)前幾篇的了解,大家應(yīng)該對(duì)Worker
有了一定的了解,SharedWorker
是Worker
的一種,它允許你在多個(gè)頁面之間共享一個(gè)Worker
。
SharedWorker
對(duì)比于Service Worker
要簡單很多,同樣也是繼承與Worker
,但是它的生命周期是獨(dú)立于頁面的。
SharedWorker
和其他Worker
的相同,都是需要同源的,它是通過url
來區(qū)分是否是同一個(gè)SharedWorker
。
使用 SharedWorker
SharedWorker
的使用很簡單,只需要在創(chuàng)建Worker
的時(shí)候,需要傳入一個(gè)name
參數(shù),這個(gè)name
參數(shù)就是SharedWorker
的標(biāo)識(shí),相同的name
會(huì)共享同一個(gè)SharedWorker
。
const sharedWorker = new SharedWorker('worker.js', 'mySharedWorker');
默認(rèn)情況下name
并不是必須的,這個(gè)時(shí)候相同的url
會(huì)共享同一個(gè)SharedWorker
。
const sharedWorker = new SharedWorker('worker.js');
創(chuàng)建SharedWorker
之后,我們就可以通過port
屬性來和SharedWorker
進(jìn)行通信了。
sharedWorker.port.onmessage = (e) => { console.log(e.data); };
在主線程中,可以通過創(chuàng)建出來的worker
對(duì)象中的port
屬性來和SharedWorker
進(jìn)行通信。
使用worker.port.onmessage
來監(jiān)聽SharedWorker
的消息。
在SharedWorker
中,可以通過self.onconnect
來監(jiān)聽SharedWorker
的連接。
在監(jiān)聽事件中會(huì)獲取到與SharedWorker
連接的port
,通過這個(gè)port
就可以和主線程進(jìn)行通信了。
self.onconnect = (e) => { const port = e.ports[0]; port.postMessage('hello'); };
通信還是通過postMessage
來進(jìn)行的。
通信
上面的通信只是一個(gè)簡單的示例,在主線程中創(chuàng)建的SharedWorker
,并使用port
來和SharedWorker
進(jìn)行通信。
通常情況下,我們會(huì)使用onmessage
來監(jiān)聽SharedWorker
的消息,使用這種方式默認(rèn)是打開狀態(tài),如果使用addEventListener
來監(jiān)聽SharedWorker
的消息,需要手動(dòng)調(diào)用start
方法來開啟。
sharedWorker.port.addEventListener('message', (e) => { console.log(e.data); }); sharedWorker.port.start();
在主線程中,我們可以通過port
來實(shí)現(xiàn)和SharedWorker
的通信。
// 發(fā)送消息 sharedWorker.port.postMessage('hello'); // 監(jiān)聽消息 sharedWorker.port.onmessage = (e) => { console.log(e.data); }; // 或者 sharedWorker.port.addEventListener('message', (e) => { console.log(e.data); }); sharedWorker.port.start();
在SharedWorker
中使用方式也是一樣的,不同的是,我們需要通過self.onconnect
來監(jiān)聽SharedWorker
的連接。
self.onconnect = (e) => { const port = e.ports[0]; // 發(fā)送消息 port.postMessage('hello'); // 監(jiān)聽消息 port.onmessage = (e) => { console.log(e.data); }; // 或者 port.addEventListener('message', (e) => { console.log(e.data); }); port.start(); };
可以看到上面監(jiān)聽的時(shí)候,獲取port
是一個(gè)數(shù)組,這是因?yàn)?code>SharedWorker可以和多個(gè)頁面進(jìn)行通信。
每個(gè)頁面都會(huì)有一個(gè)port
,通過這個(gè)port
和SharedWorker
進(jìn)行通信。
通常情況下我們會(huì)記錄下所有的port
,然后在需要的時(shí)候進(jìn)行通信。
const ports = []; self.onconnect = (e) => { const port = e.ports[0]; ports.push(port); port.onmessage = (e) => { console.log(e.data); }; }; // 發(fā)送消息 function sendMessage(message) { ports.forEach((port) => { port.postMessage(message); }); }
上面我們的案例中,我們只創(chuàng)建了一個(gè)SharedWorker
,所以只有一個(gè)port
,但是如果我們創(chuàng)建了多個(gè)SharedWorker
,那么就會(huì)有多個(gè)port
。
這里收到消息之后瀏覽器控制臺(tái)沒有任何打印信息,因?yàn)?code>SharedWorker的作用域中沒有window
對(duì)象,所以console
、alert
等方法都是無法使用的。
如果我們需要調(diào)試SharedWorker
,可以在瀏覽器地址欄中輸入chrome://inspect/#workers
,這樣就可以看到當(dāng)前頁面中的SharedWorker
。
指定頁面通信
上面我們拿到了所有的port
,但是我們并不知道這個(gè)port
是哪個(gè)頁面的,所以我們需要在創(chuàng)建SharedWorker
的時(shí)候,指定每個(gè)頁面的標(biāo)識(shí)。
但是很遺憾,SharedWorker
并沒有提供這樣的功能,所以我們需要自己實(shí)現(xiàn)。
這里我們可以使用雙數(shù)組來實(shí)現(xiàn),一個(gè)數(shù)組用來存儲(chǔ)頁面的標(biāo)識(shí),一個(gè)數(shù)組用來存儲(chǔ)port
。
const pages = []; const ports = []; self.onconnect = (e) => { const port = e.ports[0]; ports.push(port); // 獲取頁面標(biāo)識(shí) const page = Math.random().toString(36).substr(2); port.postMessage(page); pages.push(page); // 監(jiān)聽消息 port.onmessage = (e) => { const index = ports.indexOf(port); const page = pages[index]; // 這樣就可以知道是哪個(gè)頁面發(fā)來的消息了 console.log(page); }; };
有了上面的代碼,我們就可以知道是哪個(gè)頁面發(fā)來的消息了,并且也可以指定頁面發(fā)送消息。
實(shí)戰(zhàn)
上面我們已經(jīng)知道了如何使用SharedWorker
,那么我們就可以使用它來實(shí)現(xiàn)一個(gè)簡單的聊天室。
這里我們使用SharedWorker
來實(shí)現(xiàn)消息的轉(zhuǎn)發(fā),頁面之間的通信使用postMessage
。
- SharedWorker
const uuids = []; const ports = []; self.onconnect = (e) => { const port = e.ports[0]; ports.push(port); // 獲取頁面標(biāo)識(shí) const uuid = Math.random().toString(36).substr(2); uuids.push(uuid); port.postMessage({ type: 'connect', uuid: uuid }); // 監(jiān)聽消息 port.onmessage = (e) => { const index = ports.indexOf(port); const uuid = uuids[index]; // 群發(fā)消息 broadcast({ type: 'message', sender: uuid, message: e.data.message }); }; }; // 群發(fā)消息 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">發(fā)送</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); // 滾動(dòng)到底部 document.getElementById('messages').scrollTop = document.getElementById('messages').scrollHeight; }; document.getElementById('send').addEventListener('click', () => { const message = document.getElementById('message').value; if (!message) { alert('請輸入內(nèi)容') return; } const data = { type: 'message', message: message, } sharedWorker.port.postMessage(data); document.getElementById('message').value = ''; }); </script> </body> </html>
上面的代碼我們實(shí)現(xiàn)了一個(gè)簡單的聊天室,我們可以在多個(gè)頁面打開聊天室,然后在其中一個(gè)頁面發(fā)送消息,其他頁面都會(huì)收到消息。
上面的代碼中還有一個(gè)uuid
,=還可以基于這么來實(shí)現(xiàn)私聊,比如我們可以在頁面中添加一個(gè)select
,然后選擇一個(gè)用戶,然后在發(fā)送消息的時(shí)候,將uuid
傳遞過去,然后在SharedWorker
中判斷,如果是私聊,就只發(fā)送給指定的用戶。
這一塊就留給感興趣的同學(xué)自己實(shí)現(xiàn)了。
SharedWorker 的關(guān)閉
上面我們實(shí)現(xiàn)了一個(gè)簡單的聊天室,但是我們發(fā)現(xiàn),當(dāng)我們關(guān)閉頁面的時(shí)候,SharedWorker
并沒有關(guān)閉,這是因?yàn)?code>SharedWorker是一個(gè)長連接,當(dāng)我們關(guān)閉頁面的時(shí)候,SharedWorker
并沒有關(guān)閉,所以我們需要在頁面關(guān)閉的時(shí)候,手動(dòng)關(guān)閉SharedWorker
。
// 關(guān)閉頁面的時(shí)候,關(guān)閉 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; } }; };
上面的代碼中,我們在頁面關(guān)閉的時(shí)候,發(fā)送一個(gè)close
的消息,然后在SharedWorker
中,將port
從ports
中移除,這樣就可以關(guān)閉SharedWorker
了。
SharedWorker 的生命周期
SharedWorker
的生命周期沒有那么復(fù)雜,不像ServiceWorker
有那么多的過程,它只有一條規(guī)則,就是當(dāng)所有創(chuàng)建SharedWorker
的頁面關(guān)閉之后,那么SharedWorker
的生命就走到了盡頭,否則它就會(huì)一直常駐。
SharedWorker 還可以做什么?
聊天室只是一個(gè)小玩具,我們還可以做很多事情,我們最常見的就是跨頁面登錄,例如GitHub
,我們在一個(gè)頁面登錄,然后在其他頁面就可以獲取到登錄信息,這就是SharedWorker
的一個(gè)應(yīng)用場景。
還有一個(gè)應(yīng)用場景就是,我們可以在一個(gè)頁面中,監(jiān)聽一個(gè)WebSocket
,然后在其他頁面中,發(fā)送消息,這樣就可以實(shí)現(xiàn)跨頁面的WebSocket
。
其實(shí)任何跨頁面的通信,都可以使用SharedWorker
來實(shí)現(xiàn),但是SharedWorker
的缺點(diǎn)也很明顯,就是不支持IE
,所以如果你的項(xiàng)目需要兼容IE
,那么就不要使用SharedWorker
了。
優(yōu)點(diǎn)很明確,就是可以跨頁面通信,缺點(diǎn)也很明顯,就是瀏覽器兼容確實(shí)有點(diǎn)捉急。
總結(jié)
本文主要介紹了SharedWorker
的基本使用,并使用SharedWorker
實(shí)現(xiàn)了一個(gè)簡單的聊天室。
SharedWorker
的使用還是比較簡單的,不同于Worker
那么單純,SharedWorker
是一個(gè)長連接,所以我們可以在多個(gè)頁面中,共享一個(gè)SharedWorker
,這樣就可以實(shí)現(xiàn)跨頁面通信了。
不同于ServiceWorker
有那么多的限制,SharedWorker
除了兼容性其實(shí)功能非常強(qiáng)大,且在實(shí)際項(xiàng)目中的應(yīng)用場景也更加廣泛。
以上就是SharedWorker 多頁面相互通信示例詳解的詳細(xì)內(nèi)容,更多關(guān)于SharedWorker 多個(gè)頁面通信的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Layui Table js 模擬選中checkbox的例子
今天小編就為大家分享一篇Layui Table js 模擬選中checkbox的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-09-09JavaScript判斷頁面加載完之后再執(zhí)行預(yù)定函數(shù)的技巧
這篇文章主要介紹了JavaScript判斷頁面加載完之后再執(zhí)行預(yù)定函數(shù)的技巧,原理還是利用監(jiān)聽器監(jiān)聽元素事件、被觸發(fā)則執(zhí)行函數(shù),需要的朋友可以參考下2016-05-05微信小程序?qū)崿F(xiàn)跳轉(zhuǎn)詳情頁面
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)跳轉(zhuǎn)詳情頁面,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06原生JavaScript實(shí)現(xiàn)Tooltip浮動(dòng)提示框特效
這篇文章主要為大家詳細(xì)介紹了原生JavaScript設(shè)計(jì)和實(shí)現(xiàn)Tooltip浮動(dòng)提示框特效,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03純js實(shí)現(xiàn)的積木(div層)拖動(dòng)功能示例
這篇文章主要介紹了純js實(shí)現(xiàn)的積木(div層)拖動(dòng)功能,結(jié)合實(shí)例形式分析了javascript隨機(jī)生成各種顏色div層及響應(yīng)鼠標(biāo)事件改變元素屬性實(shí)現(xiàn)拖動(dòng)效果的相關(guān)操作技巧,需要的朋友可以參考下2017-07-07JS向上取整、向下取整、四舍五入、取絕對(duì)值、取較大較小值及隨機(jī)數(shù)代碼示例
在Js中對(duì)數(shù)值進(jìn)行操作的場景有,向上取整、向下取整、四舍五入、固定精度、固定長度、取整,這篇文章主要給大家介紹了關(guān)于JS向上取整、向下取整、四舍五入、取絕對(duì)值、取較大較小值及隨機(jī)數(shù)的相關(guān)資料,需要的朋友可以參考下2024-04-04微信小程序?qū)崿F(xiàn)點(diǎn)擊空白隱藏的方法示例
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)點(diǎn)擊空白隱藏的方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08