Channel Messaging API允許iframe和worker通過通道與主文檔線程進行通信,通過傳遞消息。

引言Channel Messaging API

在同一個文檔中運行的兩個腳本,但處於不同的上下文中,Channel Messaging API允許它們通過通道進行通信。

此用例涉及以下通信方式:

  • 文檔和iframe之間的通信
  • 兩個iframe之間的通信
  • 兩個文檔之間的通信

它是如何工作的

調用new MessageChannel()來初始化消息通道。

const channel = new MessageChannel()

該通道具有2個屬性,稱為

  • port1
  • port2

這些屬性均為MessagePort對象。port1是由創建通道的一方使用的端口,而port2是通道接收器使用的端口(順帶一提,通道是雙向的,因此接收器也可以發送消息)。

在通道的每一端,您都可以在一個端口上進行侦聽,並將消息發送到另一個端口。

通過otherWindow.postMessage()方法發送消息,其中otherWindow是另一個瀏覽上下文。它接受一個消息,一個源和一個端口。

例如:

const data = { name: 'Flavio' }
const channel = new MessageChannel()
window.postMessage(data, [channel.port2])

消息可以是下列支援的值之一:

  • 所有原始類型,但排除符號
  • 數組
  • 物件文本
  • 字符串,日期,正則表達式對象
  • Blob,File,FileList對象
  • ArrayBuffer,ArrayBufferView對象
  • FormData對象
  • ImageData對象
  • Map和Set對象

“源”是一個URI(例如https://example.org)。您可以使用'*'來允許不嚴格的檢查,或指定一個域,或者指定'/'以設置同一域目標,而無需指定所屬域。

其他瀏覽上下文使用message事件來聆聽消息:

self.addEventListener('message', event => {
    console.log('A new message arrived!')
})

self在這種情況下與使用window相同。

在事件處理程序內部,我們可以通過查看事件對象的data屬性來訪問發送的數據:

self.addEventListener('message', event => {
    console.log('A new message arrived!')
    console.log(event.data)
})

我們可以通過使用MessagePort.postMessage來回應:

self.addEventListener('message', event => {
    console.log('A new message arrived!')
    console.log(event.data)
    
    const data = { someData: 'hey' }
    event.ports[0].postMessage(data)
})

通過在端口上調用close()方法,可以關閉通道:

self.addEventListener('message', event => {
 console.log('A new message arrived!')
 console.log(event.data)

 const data = { someData: 'hey' }
 event.ports[0].postMessage(data)
 event.ports[0].close()
})

iframe的示例

以下是文檔和嵌入其中的iframe之間發生的通信示例。

主文檔定義一個iframe和一個span,我們將在其中打印從iframe文檔發送的消息。一旦iframe文檔加載完畢,我們就在我們創建的channel上給它發送一個消息。

<!DOCTYPE html>
<html>
    <body>
        <iframe src="iframe.html" width="500" height="500"></iframe>
        <span></span>
    </body>
    <script>
        const channel = new MessageChannel()
        const display = document.querySelector('span')
        const iframe = document.querySelector('iframe')

        iframe.addEventListener('load', () => {
            iframe.contentWindow.postMessage('Hey', '*', [channel.port2])
        }, false)

        channel.port1.onmessage = event => {
            display.innerHTML = event.data
        }
    </script>
</html>

iframe頁面的源代碼更加簡單:

<!DOCTYPE html>
<html>
    <script>
        window.addEventListener('message', event => {
            // send a message back
            event.ports[0].postMessage('Message back from the iframe')
        }, false)
    </script>
</html>

正如您所看到的,我們甚至不需要初始化一個channel,因為當從容器頁面接收到消息時,window.onmessage處理程序會自動運行。

發送的事件由以下屬性組成:

  • data:從其他窗口發送的對象
  • origin:發送消息的窗口的源URI
  • source:發送消息的窗口對象

始終驗證消息發送者的來源。

e.ports[0]是我們在iframe中引用port2的方式,因為ports是一個數組,並且該端口被添加到第一個元素。

在我的回答中翻译

Service Worker的示例

Service Worker是事件驅動的worker,其是與web頁面關聯的JavaScript文件。請查看Service Workers指南以了解更多有關它們的信息。

重要的是要知道Service Worker與主線程隔離,我們必須使用消息與它們進行通信。

以下是附加到主文檔的腳本如何處理發送消息給Service Worker:

// `worker`是已實例化的Service Worker

const messageChannel = new MessageChannel()
messageChannel.port1.addEventListener('message', event => {
    console.log(event.data)
})
worker.postMessage(data, [messageChannel.port2])

在Service Worker代碼中,我們為message事件添加了一個事件監聽器:

self.addEventListener('message', event => {
    console.log(event.data)
})

它可以通過將消息發送到messageChannel.port2來發送消息回來:

self.addEventListener('message', event => {
    event.ports[0].postMessage(data)
})

瀏覽器支援

Channel Messaging API當前得到了所有主要瀏覽器的支援,其中很多瀏覽器已經支援了許多年,因此即使是舊版本的瀏覽器也支援它。有關詳細信息,請參閱https://caniuse.com/#feat=channel-messaging