iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
Modern Web

網頁的另一個大腦:從基礎到進階掌握 Web Worker 技術系列 第 9

Transferable objects - MessagePort (Channel messaging)

  • 分享至 

  • xImage
  •  

MessagePort 是 HTML 中 Channel Messaging API 的接口,用途拿來進行雙向溝通傳遞資料,有點類似 addEventListener 或是 vue2 的 EventBus 這種發布訂閱模式,使用方式如下:

創建 MessageChannel

創建的實例 channel 會有兩個屬性,port1port2,有點像是小時候玩的傳聲筒(可以用養樂多罐中間串著一條線),兩側的養樂多罐分別代表 port1port2,雙方都可以傳送聲音到另一側或是從另一側接收到聲音

const channel = new MessageChannel();
const port1 = channel.port1; // MessagePort 接口
const port2 = channel.port2; // MessagePort 接口

傳遞接收資料

接著兩邊的 port 就可以使用 postMessageonmessage 互相傳送、接收訊息:

// port1 發送訊息,port2 接收訊息
port2.onmessage = (e) => {
  console.log('從 port1 傳來的訊息', e.data) // 顯示 port1
};
port1.postMessage('port1');

// 或是反過來由 port2 傳送訊息,port1 接收訊息
port1.onmessage = (e) => {
  console.log('從 port2 傳來的訊息', e.data) // 顯示 port2
};
port2.postMessage('port2');

驗證資料來源

由於 MessageChannel 可以從各種來源接收到資料,所以在接收資料時,最好都要先檢查 e.origin 是否為可信任的網域

port1.onmessage = (e) => {
  // 判斷來源為可信任網域
  if (e.origin === 'https://my-website.com')  {
    // 執行後續邏輯
  }
};

使用時機

Channel messaging 常拿來使用的場景是與 iframe 之間的溝通,假設有一個社交網站開發了一個遊戲,而這個遊戲是額外用 iframe 去執行的,當使用者玩完這個遊戲的時候,通常會有紀錄最高分的功能,需要將使用者獲得的分數從 iframe 傳遞到原本網站的主頁面,如此一來就可以使用 Channel messaging 來進行資料的傳遞或互動。

範例

接著拿 MDN 的官方範例來解釋:
以下 Demo 展示了一個主頁面與 iframe 之間的訊息傳遞過程:

Step 0.
主頁面先建立 MessageChannel 實例,接著把 port2 傳遞給 iframe 使用
Step 1.
主頁面會將使用者輸入的訊息傳遞給 iframe
Step 2.
iframe 接收到訊息後,用列表的方式顯示出來
Step 3.
接著 iframe 又會將剛接收到的訊息傳回給主頁面
Step 4.
最後主頁面接到從 iframe 傳來的訊息後,會在最上面顯示出來

https://ithelp.ithome.com.tw/upload/images/20230923/20162687g0osFWRp5X.png

index.html (主頁面)

<body>
  <iframe src="page2.html" width="480" height="320"></iframe>
  <script>
    const iframe = document.querySelector("iframe");
    const channel = new MessageChannel();
    const port1 = channel.port1; // port1 代表主頁面的溝通埠

    iframe.addEventListener("load", onLoad);

    function onLoad() {
      button.addEventListener("click", onClick);
      port1.onmessage = onMessage;
      // Step 0. 
      // 最一開始先將 port2 傳遞給 iframe,使 iframe 可以藉由 port2 發送接收訊息
      iframe.contentWindow.postMessage("init", "*", [channel.port2]);
    }

    // Step 1. 使用者按下送出後,將輸入的值從 port1 送出
    function onClick(e) {
      e.preventDefault();
      port1.postMessage(input.value);
    }

    // Step 4. 主頁面接收到 iframe 傳來的訊息後,顯示在畫面上
    function onMessage(e) {
      output.innerHTML = e.data;
      input.value = "";
    }
  </script>
</body>

page2.html (iframe)

<body>
  <ul></ul>
  <script>
    const list = document.querySelector("ul");
    let port2;

    // 初次接收到訊息,初始化設定 port2
    window.addEventListener("message", initPort);

    function initPort(e) {
      port2 = e.ports[0];
      // 設定完 port2 後,可以監聽 port1 傳來的訊息
      port2.onmessage = onMessage;
    }

    // Step 2. 接收到主頁面傳來的訊息後,渲染到 iframe 裡的列表上
    function onMessage(e) {
      const listItem = document.createElement("li");
      listItem.textContent = e.data;
      list.appendChild(listItem);
      // Step 3. 接著再把接收到的訊息回傳給主頁面
      port2.postMessage('Message received by IFrame: "' + e.data + '"');
    }
  </script>
</body>

其中 iframe.contentWindow.postMessage 在一開始時將 port2 傳遞給 iframe,讓 iframe 可以操作 port2 來發送、接收訊息

  1. 第一個參數 "init",是傳送給 iframe 的訊息,單純讓 iframe 知道這是第一次的初始化
  2. 第二個參數 "*" ,代表這個訊息可以傳遞給任意來源網址的 iframe
  3. 第三個參數 "[channel.port2]",代表著要把 port2 的使用權轉移到 iframe
iframe.contentWindow.postMessage("init", "*", [channel.port2]);

以上的範例介紹了 Channel messaging 的使用方式,但其實我們今天都還沒講到 MessagePort 身為 Transferable objects 的作用,這一點我們明天再來看 MessagePort 如何應用在 web worker 裡。

Reference

MDN - Using channel messaging


上一篇
Transferable objects - ArrayBuffer
下一篇
Transferable objects - MessagePort
系列文
網頁的另一個大腦:從基礎到進階掌握 Web Worker 技術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言