postMessage()
是 JavaScript 中一个用于在不同的窗口、iframe 或者标签页之间进行跨域通信的 API。它通过一种安全的方式允许文档之间发送消息,而不要求它们在同一个源(域名、协议、端口)上。下面详细讲解 postMessage()
相关的知识点。
1. 基本概念
postMessage()
是 HTML5 引入的 API,允许一个窗口发送消息到另一个窗口,无论它们是否同源。它广泛用于 Web 应用中,尤其是在现代网页应用程序中,用来在主页面和 iframe、弹出窗口、服务端之间进行通信。
2. 语法
window.postMessage(message, targetOrigin, [transfer]);
- message:要发送的消息,可以是字符串或是任何可序列化的数据(例如对象、数组)。消息内容会通过结构化克隆算法进行传递,类似 JSON 序列化。
- targetOrigin:指定可以接收消息的窗口的源(协议、域名、端口),以确保消息不会被发送到未知或不信任的站点。可以使用
"*"
表示不限制目标源,但这样可能会带来安全隐患。 - transfer(可选):传递的对象,通常是像
ArrayBuffer
或者MessagePort
这样的对象,这些对象会通过转移的方式而不是拷贝传递。
3. 消息接收
要接收通过 postMessage()
发送的消息,目标窗口必须监听 message
事件。可以通过以下方式注册事件处理程序:
window.addEventListener("message", function(event) {
// 检查消息的来源是否可信
if (event.origin !== "https://trusted-origin.com") {
return;
}
// 处理接收到的消息
console.log("Message received:", event.data);
});
4. 安全性
跨域通信中,安全性是最为重要的考虑因素。为了避免消息被不可信的站点截获或伪造,建议遵循以下安全性规则:
-
targetOrigin 的使用:总是显式指定目标源,而不是使用
"*"
,这样可以确保消息只会发送到特定的站点。window.postMessage("Hello", "https://trusted-site.com");
-
验证消息来源:在接收到消息时,检查
event.origin
是否为可信的源,避免处理来自恶意站点的消息。 -
消息的校验:在处理接收到的消息之前,确保对消息的内容进行校验,防止潜在的恶意代码注入攻击。
5. postMessage()
的应用场景
5.1. iframe 与主页面通信
postMessage()
最常见的使用场景之一是在主页面与嵌入的 iframe 之间进行通信。无论它们是否属于同一个域名,都可以通过 postMessage()
来实现安全通信。
示例:主页面发送消息给 iframe
<!-- 主页面 -->
<iframe id="myFrame" src="https://other-domain.com"></iframe>
<script>
var iframe = document.getElementById('myFrame');
iframe.contentWindow.postMessage('Hello iframe', 'https://other-domain.com');
</script>
示例:iframe 接收消息
<!-- iframe 页面 -->
<script>
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-domain.com') {
return;
}
console.log('Received message from parent:', event.data);
});
</script>
5.2. 跨窗口通信
在一个页面中打开了一个新窗口,可以使用 postMessage()
在父窗口和子窗口之间进行通信。
示例:父窗口发送消息给子窗口
var popup = window.open("https://other-domain.com");
popup.postMessage('Hello popup', 'https://other-domain.com');
示例:子窗口接收消息
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-domain.com') {
return;
}
console.log('Message from parent window:', event.data);
});
5.3. Web Workers 与主线程通信
postMessage()
也可以用于 Web Worker 和主线程之间的通信。由于 Web Worker 没有直接访问 DOM 的权限,它们需要与主线程进行通信来完成任务。
示例:主线程发送消息给 Web Worker
var worker = new Worker('worker.js');
worker.postMessage('Hello Worker');
示例:Web Worker 接收消息
// worker.js
self.addEventListener('message', function(event) {
console.log('Message from main thread:', event.data);
});
5.4. Service Worker 与页面通信
Service Worker 也是一种场景,页面可以使用 postMessage()
与 Service Worker 通信,实现一些后台任务的交互。
6. event
对象的属性
当目标窗口接收到 message
事件时,event
对象会包含以下属性:
- data:发送的消息内容。
- origin:消息来源的协议、域名和端口。
- source:发送消息的窗口对象,允许我们回复发送方。
- lastEventId:消息的唯一标识符(通常在服务器发送事件中使用)。
- ports:如果消息涉及
MessagePort
对象,则可以通过此属性获取传递的端口。
7. 注意事项
- 跨域限制:
postMessage()
允许跨域发送消息,但前提是接收方必须显式监听message
事件,否则消息不会被接收。 - 消息序列化:消息数据在传递过程中会被序列化(结构化克隆),因此一些复杂对象(如 DOM 元素、函数)无法直接传递。
- 性能:传递大型对象时,建议使用
transfer
参数以传递而非拷贝数据,从而提高性能。
8. 其他进阶用法
8.1. 传递 MessagePort
postMessage()
支持 MessagePort
传递,可以用于创建双向通信通道。
示例:传递 MessagePort
// 在 iframe 页面中创建一个 MessageChannel
var channel = new MessageChannel();
// 将一端 port 发送给主页面
parent.postMessage('Here is a port', 'https://trusted-domain.com', [channel.port2]);
// 监听消息
channel.port1.onmessage = function(event) {
console.log('Received message through channel:', event.data);
};
9. 总结
postMessage()
是一个强大而灵活的 API,能够实现不同窗口、iframe、Web Worker 以及其他上下文之间的安全通信。虽然它简化了跨域通信的实现,但也带来了潜在的安全风险。使用 postMessage()
时,必须注意确保消息的发送和接收过程都是可信的,尤其是要谨慎指定 targetOrigin
和验证消息来源的 origin
属性。
10. 完整示例
以下是一个完整的跨窗口通信示例,演示了如何使用 postMessage()
在两个不同的窗口之间传递消息:
<!-- 父页面 -->
<!DOCTYPE html>
<html>
<head>
<title>Parent Window</title>
</head>
<body>
<button id="sendMessage">Send Message to Child</button>
<script>
var popup = window.open('child.html', 'ChildWindow', 'width=400,height=400');
document.getElementById('sendMessage').addEventListener('click', function() {
popup.postMessage('Hello from parent', 'http://example.com');
});
window.addEventListener('message', function(event) {
if (event.origin !== 'http://example.com') return;
console.log('Received message from child:', event.data);
});
</script>
</body>
</html>
<!-- 子页面 (child.html) -->
<!DOCTYPE html>
<html>
<head>
<title>Child Window</title>
</head>
<body>
<script>
window.addEventListener('message', function(event) {
if (event.origin !== 'http://example.com') return;
console.log('Received message from parent:', event.data);
event.source.postMessage('Hello from child', event.origin);
});
</script>
</body>
</html>
这个示例展示了父窗口和子窗口如何通过 postMessage()
进行双向通信,并通过 event.origin
验证消息来源的安全性。