實現多個標籤頁之間通訊的幾種方法

wuliToken發表於2019-03-04

效果圖.gif
示例地址

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.jsindex.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的資料。
  • 參考:developer.mozilla.org/zh-CN/docs/…

相關文章