prologue
- 之前在網上看到一個面試題:如何實現瀏覽器中多個標籤頁之間的通訊。我目前想到的方法有三種:使用websocket協議、通過localstorage、以及使用html5瀏覽器的新特性SharedWorker。
- websocket這裡先不介紹了,全雙工(full-duplex)通訊自然可以實現多個標籤頁之間的通訊,相信網上通過websocket實現聊天室的教程也不少,我自己也用socket.io寫了一個線上聊天室
- 接下來會介紹另外兩個方法:監聽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
在同一目錄。 -
因為客戶端和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的資料。 - 參考:developer.mozilla.org/zh-CN/docs/…