window.postMessage 通信指南
基础概念
window.postMessage()
是一种跨源通信的方法,允许来自不同源(域、协议或端口)的页面之间安全地进行通信。这种机制提供了一种受控的方式来规避同源策略的限制。
基础用法
发送消息
javascript
targetWindow.postMessage(message, targetOrigin, [transfer]);
参数说明:
targetWindow
:接收消息的窗口对象,如 iframe 的 contentWindow 属性、window.open 返回的窗口对象或命名的窗口/框架message
:要发送的数据,会被结构化克隆算法序列化targetOrigin
:指定接收消息的窗口的源,可以是具体的 URL 或 "*"(表示任意源)transfer
:(可选)是一组可转移对象,这些对象的所有权将被转移给接收方
接收消息
javascript
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
// 验证发送方的源
if (event.origin !== "https://trusted-domain.com") return;
// 处理消息
console.log("收到消息:", event.data);
// 可以回复消息
event.source.postMessage("收到你的消息", event.origin);
}
event 对象的主要属性:
data
:从其他窗口发送过来的数据origin
:发送消息的窗口的源source
:发送消息的窗口对象的引用ports
:MessageChannel 的端口对象数组
使用场景
- iframe 通信:父页面与嵌入的 iframe 之间的数据交换
- 跨域窗口通信:通过 window.open 打开的不同域的窗口间通信
- Web Worker 通信:与 Web Worker 或 Service Worker 进行通信
- 第三方集成:与嵌入的第三方小部件或应用进行安全通信
- 单页应用路由:在复杂的单页应用中,不同路由间的状态同步
- 微前端架构:在微前端架构中,不同子应用间的数据交换和状态同步
安全注意事项
-
始终验证消息来源:
javascriptif (event.origin !== "https://trusted-domain.com") return;
-
避免使用 "*" 作为 targetOrigin: 尽量指定确切的目标源,而不是使用通配符 "*",以防止信息泄露给恶意网站。
-
验证消息内容: 不要假设收到的消息格式是正确的,始终进行验证和类型检查。
javascriptif (typeof event.data !== "object" || !event.data.type) return;
-
避免执行来自消息的代码: 永远不要直接执行从消息中接收到的代码,如 eval(event.data)。
-
限制消息频率: 实现节流或防抖机制,防止消息风暴导致性能问题。
-
处理错误: 使用 try-catch 块处理消息处理过程中可能出现的错误。
性能考虑
- 消息大小:避免传输大量数据,这可能导致性能问题。
- 消息频率:控制消息发送的频率,避免过多的通信开销。
- 结构化克隆限制:了解结构化克隆算法的限制,如不能克隆函数、DOM 节点等。
浏览器兼容性
window.postMessage
在所有现代浏览器中都得到良好支持,包括 Chrome、Firefox、Safari、Edge 和 IE11。caniuse查看兼容性

调试技巧
- 使用浏览器开发者工具监控 message 事件
- 添加详细的日志记录
- 实现消息的确认机制
- 使用唯一标识符跟踪消息
最佳实践
-
消息格式标准化:
javascript{ type: "ACTION_TYPE", payload: {}, // 实际数据 id: "unique-id", // 用于跟踪 timestamp: Date.now() }
-
实现请求-响应模式:
javascript// 发送方 const messageId = generateUniqueId(); const responsePromise = createResponsePromise(messageId); iframe.contentWindow.postMessage({ type: "REQUEST_DATA", id: messageId }, "https://trusted-domain.com"); responsePromise.then(response => { console.log("收到响应:", response); }); // 接收方 window.addEventListener("message", event => { if (event.origin !== "https://parent-domain.com") return; if (event.data.type === "REQUEST_DATA") { event.source.postMessage({ type: "RESPONSE_DATA", id: event.data.id, payload: { result: "some data" } }, event.origin); } });
-
错误处理: 在消息协议中包含错误处理机制。
-
版本控制: 在消息中包含版本信息,以便处理 API 变更。
案例效果

vue3-project\public\child.html 下新建一个html
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PostMessage 子页面</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background-color: #f0f8ff;
margin: 0;
}
.child-container {
padding: 15px;
border: 2px solid #4682b4;
border-radius: 8px;
}
h2 {
color: #4682b4;
margin-top: 0;
}
.input-group {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 15px;
}
input {
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
flex-grow: 1;
}
button {
background-color: #4682b4;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #3a6d99;
}
.message-display {
background-color: white;
padding: 10px;
border-radius: 4px;
border-left: 4px solid #4682b4;
margin-top: 15px;
}
.message-content {
font-weight: bold;
word-break: break-word;
}
</style>
</head>
<body>
<div class="child-container">
<h2>PostMessage 子页面</h2>
<div class="input-group">
<label for="message-input">发送消息:</label>
<input
id="message-input"
type="text"
placeholder="输入要发送的消息"
value="你好,父页面!"
/>
<button id="send-button">发送到父页面</button>
</div>
<div id="message-display" class="message-display" style="display: none">
<h3>收到来自父页面的消息:</h3>
<div id="message-content" class="message-content"></div>
</div>
</div>
<script>
// 页面元素
const messageInput = document.getElementById('message-input')
const sendButton = document.getElementById('send-button')
const messageDisplay = document.getElementById('message-display')
const messageContent = document.getElementById('message-content')
/**
* 发送消息到父窗口
*/
function sendMessageToParent() {
try {
// 发送消息到父窗口
window.parent.postMessage(
{
type: 'CHILD_MESSAGE',
payload: messageInput.value,
timestamp: Date.now(),
},
'*'
) // 在生产环境中应该指定具体的目标源
} catch (error) {
console.error('发送消息失败:', error)
}
}
/**
* 接收来自父窗口的消息
*/
function handleMessage(event) {
// 在实际应用中应该验证消息来源
// if (event.origin !== 'https://trusted-domain.com') return;
try {
// 处理接收到的消息
if (event.data && event.data.type === 'PARENT_MESSAGE') {
messageContent.textContent = event.data.payload
messageDisplay.style.display = 'block'
}
} catch (error) {
console.error('处理消息失败:', error)
}
}
// 添加事件监听器
sendButton.addEventListener('click', sendMessageToParent)
window.addEventListener('message', handleMessage)
// 页面加载完成后通知父窗口
window.addEventListener('load', function () {
try {
window.parent.postMessage(
{
type: 'CHILD_LOADED',
payload: '子页面已加载完成',
timestamp: Date.now(),
},
'*'
)
} catch (error) {
console.error('通知父窗口失败:', error)
}
})
</script>
</body>
</html>
vue3-project\src\views\PostMessageView.vue
html
<script setup>
import ParentComponent from '../components/PostMessageDemo/ParentComponent.vue'
</script>
<template>
<div class="post-message-view">
<h1>window.postMessage 通信演示</h1>
<div class="description">
<p>
本示例演示了如何使用
<code>window.postMessage</code> 在父页面和嵌入的iframe之间进行安全通信。
您可以在下方的输入框中输入消息并发送,然后观察两个页面之间的通信过程。
</p>
</div>
<ParentComponent />
<div class="documentation-link">
<p>
查看
<a href="#" @click.prevent="downloadDoc">完整文档</a>
了解更多关于window.postMessage的用法和最佳实践。
</p>
</div>
</div>
</template>
<script>
// 导出组件以允许使用选项API添加方法
export default {
methods: {
// 下载文档方法
downloadDoc() {
// 在实际应用中,这里可以链接到文档或触发文档下载
window.open('/docs/postMessage.md', '_blank')
},
},
}
</script>
<style scoped>
.post-message-view {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1 {
color: #42b883;
text-align: center;
margin-bottom: 30px;
}
.description {
background-color: #f8f8f8;
padding: 15px;
border-radius: 8px;
margin-bottom: 30px;
border-left: 4px solid #42b883;
}
code {
background-color: #e8e8e8;
padding: 2px 5px;
border-radius: 3px;
font-family: monospace;
}
.documentation-link {
margin-top: 30px;
text-align: center;
}
.documentation-link a {
color: #42b883;
text-decoration: none;
font-weight: bold;
}
.documentation-link a:hover {
text-decoration: underline;
}
</style>
vue3-project\src\components\PostMessageDemo\ParentComponent.vue
html
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// 通信状态
const messageReceived = ref(null)
const messageToSend = ref('你好,子页面!')
const iframeLoaded = ref(false)
// iframe引用
const iframeRef = ref(null)
/**
* 发送消息到iframe
*/
const sendMessage = () => {
if (!iframeRef.value || !iframeLoaded.value) return
try {
// 发送消息到iframe
iframeRef.value.contentWindow.postMessage(
{
type: 'PARENT_MESSAGE',
payload: messageToSend.value,
timestamp: Date.now(),
},
'*'
) // 在生产环境中应该指定具体的目标源
} catch (error) {
console.error('发送消息失败:', error)
}
}
/**
* 接收来自iframe的消息
*/
const handleMessage = (event) => {
// 在实际应用中应该验证消息来源
// if (event.origin !== 'https://trusted-domain.com') return;
try {
// 处理接收到的消息
if (event.data && event.data.type === 'CHILD_MESSAGE') {
messageReceived.value = event.data.payload
}
} catch (error) {
console.error('处理消息失败:', error)
}
}
/**
* 处理iframe加载完成事件
*/
const handleIframeLoad = () => {
iframeLoaded.value = true
}
// 组件挂载时添加消息监听器
onMounted(() => {
window.addEventListener('message', handleMessage)
})
// 组件卸载时移除消息监听器
onUnmounted(() => {
window.removeEventListener('message', handleMessage)
})
</script>
<template>
<div class="parent-container">
<h2>PostMessage 父组件示例</h2>
<div class="control-panel">
<div class="input-group">
<label for="message-input">发送消息:</label>
<input
id="message-input"
v-model="messageToSend"
type="text"
placeholder="输入要发送的消息"
/>
<button @click="sendMessage" :disabled="!iframeLoaded">发送到iframe</button>
</div>
<div class="message-display" v-if="messageReceived">
<h3>收到来自iframe的消息:</h3>
<div class="message-content">{{ messageReceived }}</div>
</div>
</div>
<div class="iframe-container">
<iframe
ref="iframeRef"
src="/child.html"
@load="handleIframeLoad"
width="100%"
height="300"
></iframe>
</div>
</div>
</template>
<style scoped>
.parent-container {
padding: 20px;
border: 2px solid #42b883;
border-radius: 8px;
margin: 20px 0;
}
.control-panel {
margin-bottom: 20px;
}
.input-group {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 15px;
}
input {
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
flex-grow: 1;
}
button {
background-color: #42b883;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #33a06f;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.message-display {
background-color: #f8f8f8;
padding: 10px;
border-radius: 4px;
border-left: 4px solid #42b883;
}
.message-content {
font-weight: bold;
word-break: break-word;
}
.iframe-container {
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
}
iframe {
border: none;
}
</style>
结尾
看懂上面的解释和说明结合vue3小案例,是不是瞬间完全明白 postMessage
怎么玩的了;以及需要注意些什么!!!