
效果圖.gif
prologue
- 之前在網上看到一個面試題:如何實現瀏覽器中多個標簽頁之間的通信。我目前想到的方法有三種:使用websocket協議、通過localstorage、以及使用html5瀏覽器的新特性SharedWorker。
- websocket這里先不介紹了,全雙工(full-duplex)通信自然可以實現多個標簽頁之間的通信,相信網上通過websocket實現聊天室的教程也不少
- 接下來會介紹另外兩個方法:監聽localstorage和使用SharedWorker
localstorage
- localstorage是瀏覽器多個標簽共用的存儲空間,所以可以用來實現多標簽之間的通信(ps:session是會話級的存儲空間,每個標簽頁都是單獨的)。
- 直接在window對象上添加監聽即可:
window.onstorage = (e) => {console.log(e)} // 或者這樣 window.addEventListener('storage', (e) => console.log(e))
- onstorage以及storage事件,針對都是非當前頁面對localStorage進行修改時才會觸發,當前頁面修改localStorage不會觸發監聽函數。然后就是在對原有的數據的值進行修改時才會觸發,比如原本已經有一個key會a值為b的localStorage,你再執行:
localStorage.setItem('a', 'b')
代碼,同樣是不會觸發監聽函數的。
webworker
- 我們都知道JavaScript是單線程的,但是瀏覽器是擁有過個線程的比如:gui渲染線程、JS引擎線程、事件觸發線程、異步http請求線程。
- webworker作為瀏覽器的一個新特性,可以提供一個額外的線程來執行一些js代碼,並且不會影響到瀏覽器用戶界面。
- 應用場景:比如頁面中包含耗時較大的算法代碼時,就會阻塞線程影響瀏覽器渲染等等。這時候就可把耗時代碼,放到webworker(另一個線程)中執行。
- 注意,這種多線程能力不是JavaScript語言原生具有的,而是瀏覽器宿主環境提供的。
- 普通的webworker直接使用
new Worker()
即可創建,這種webworker是當前頁面專有的。然后還有種共享worker(SharedWorker),這種是可以多個標簽頁、iframe共同使用的,接下來介紹如何使用SharedWorker實現標簽頁之間的通信。
SharedWorker
- SharedWorker可以被多個window共同使用,但必須保證這些標簽頁都是同源的(相同的協議,主機和端口號)
- 首先新建一個js文件
worker.js
,具體代碼如下:
// sharedWorker所要用到的js文件,不必打包到項目中,直接放到服務器即可 let data = '' onconnect = function (e) { let port = e.ports[0] port.onmessage = function (e) { if (e.data === 'get') { port.postMessage(data) } else { data = e.data } } }
-
webworker端(暫且這樣稱呼)的代碼就如上,只需注冊一個onmessage監聽信息的事件,客戶端(即使用sharedWorker的標簽頁)發送message時就會觸發。
-
注意webworker無法在本地使用,出於瀏覽器本身的安全機制,所以我這次的示例也是放在服務器上的,
worker.js
和index.html
在同一目錄。
image -
因為客戶端和webworker端的通信不像websocket那樣是全雙工的,所以客戶端發送數據和接收數據要分成兩步來處理。示例中會有兩個按鈕,分別對應的向sharedWorker發送數據的請求以及獲取數據的請求,但他們本質上都是相同的事件--發送消息。
-
webworker端會進行判斷,傳遞的數據為'get'時,就把變量data的值回傳給客戶端,其他情況,則把客戶端傳遞過來的數據存儲到data變量中。下面是客戶端的代碼:
// 這段代碼是必須的,打開頁面后注冊SharedWorker,顯示指定worker.port.start()方法建立與worker間的連接 if (typeof Worker === "undefined") { alert('當前瀏覽器不支持webworker') } else { let worker = new SharedWorker('worker.js') worker.port.addEventListener('message', (e) => { console.log('來自worker的數據:', e.data) }, false) worker.port.start() window.worker = worker } // 獲取和發送消息都是調用postMessage方法,我這里約定的是傳遞'get'表示獲取數據。 window.worker.port.postMessage('get') window.worker.port.postMessage('發送信息給worker')
- 頁面A發送數據給worker,然后打開頁面B,調用
window.worker.port.postMessage('get')
,即可收到頁面A發送給worker的數據。 - 參考:https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Using_web_workers