前幾天學習關於 Web worker
的知識時,我發現不同地方都會使用 postMessage
函式把訊息發送出去,但不同地方使用 postMessage
的方式都有點不同,所以今天這篇文章希望統整這些 postMessage
的使用方式與差異
目前 MDN 上列出的大致上有六種
window.postMessage
通常使用在需要與 iframe
溝通的場景
當主頁面中有 iframe
存在,可以使用 iframe.contentWindow 取得 iframe
中的 window
const iframe = document.querySelector("iframe");
iframe.onload = () => {
// 取得 iframe 裡的 window 屬性
const iframeWindow = iframe.contentWindow;
};
取得 iframeWindow 後,就可以直接利用這個屬性去操作 iframe 裡的一些東西,例如改變整個 iframe 網頁的背景顏色:
iframeWindow.document.querySelector("body").style.backgroundColor = "blue";
但有個問題是,以上的操作都限定在主頁面與 iframe
網頁是同源網站,如果主頁面跟 iframe
網頁不同源的話,瀏覽器會限制只能拿到某些特定的 window
的屬性 (ex. window.opener、window.location ),被限制可以拿到的屬性請看 跨域腳本API訪問
所以當不同的 window
, iframe
間不同源的狀況下,無法使用上面的方式拿到另一個頁面的資料,此時需要使用 window.postMessage
window.postMessage(message, targetOrigin, [transfer])
structuredClone
算法複製後再傳送"*"
代表傳遞訊息到任意網域,但避免將洩漏敏感訊息到惡意網站,最好都提供固定的網域地址Transferable objects
轉移物件所有權到另一個目標視窗接著在另一邊的網頁使用 addEventListener('message')
接收 window.postMessage
傳來的訊息,為了避免從任意網站接收到惡意訊息,執行任何操作前都應該先檢查 訊息的發送網域(event.origin) 是否為可信任的
window.addEventListener('message', (event) => {
// 確認訊息來源為可信任網域,執行接下來的操作
if (event.origin === 'http://trust-website.org') {
// 從 e.data 拿出訊息
console.log(event.data);
}
})
以上述更改 iframe
網頁的背景顏色為例,改用 window.postMessage
的寫法如下:
// 主視窗 (http://main.example.com)
const iframe = document.querySelector("iframe");
iframe.onload = () => {
// 取得 iframe 裡的 window 屬性
const iframeWindow = iframe.contentWindow;
iframeWindow.postMessage({ backgroundColor: 'blue' }, );
};
// iframe 視窗 (http://iframe.example.com)
window.addEventListener('message', (e) => {
if (event.origin === 'http://main.example.com') {
const { backgroundColor } = e.data;
document.querySelector("body").style.backgroundColor = backgroundColor;
}
});
Web worker 中的 postMessage 在之前的文章中已經多次出現,可以前往 第三天的文章 - Dedicated worker 基本用法,查看更多細節的介紹
Service Worker 是瀏覽器進行網路請求時的中介層,在向 server 送出請求獲取 html, image, js 等資源的時候,可以中途攔截請求,直接回傳之前已經快取過的檔案,達到離線狀態也能正常瀏覽網頁的功能
ServiceWorker.postMessage
可以從主視窗向 service worker
發送訊息:
// 主視窗
// 註冊 service worker
navigator.serviceWorker.register("service-worker.js");
navigator.serviceWorker.ready.then((registration) => {
// 發送訊息到 service-worker.js
registration.active.postMessage(
"Test message sent immediately after creation",
);
});
在 service-worker.js
檔案裡,獲得從主視窗傳來的訊息:
// service-worker.js
addEventListener("message", (event) => {
console.log(`Message received: ${event.data}`);
});
在 Service worker 中,client 是指通過 Service worker 註冊過的頁面
Client.postMessage
基本上就是 ServiceWorker.postMessage
反向傳遞訊息的用法,Client.postMessage
負責從 service worker
檔案中,傳遞訊息到主視窗
以下範例傳遞訊息到所有 註冊過 service-worker.js 的頁面,首先呼叫 Clients.matchAll() 取得所有註冊過 Service worker
的頁面,接著再使用 Client.postMessage
傳遞訊息
// service-worker.js
self.clients.matchAll().then(function(clients) {
clients.forEach(function(client) {
client.postMessage('傳遞到主視窗的訊息');
});
});
請參考 第九天的文章 - Transferable objects - MessagePort (Channel messaging)
請參考 第十一天的文章 - BroadcastChannel
不同的場景分別有不同的 postMessage
使用方式,共同點是每種 postMessage
都使用 structuredClone
複製數據後再傳遞訊息,而 是否可以傳遞 Transferable objects 及 是否有同源限制 則有所差異
種類 | structuredClone | transfer 參數 | 限制只能同源使用 |
---|---|---|---|
window.postMessage | O | O | X |
Worker.postMessage | O | O | O |
ServiceWorker.postMessage | O | O | O |
Client.postMessage | O | O | O |
MessagePort.postMessage | O | O | X |
BroadcastChannel.postMessage | O | X | O |
運用 postMessage 解決 iframe 與父層溝通的問題
postMessage:主頁、iframe 頁可互相傳值
postMessage可太有用了
如何与 Service Worker 通信