postMessage 是 Web API 中用于跨文档通信(Cross-document Messaging)的方法,但需要注意的是,它并不是 HTTP 协议的一部分,而是浏览器提供的 JavaScript API。它通常用于以下场景:
javascript// 发送消息到另一个窗口或 iframe
const targetWindow = document.getElementById('iframe').contentWindow;
targetWindow.postMessage('Hello from parent!', 'https://example.com');
// 发送到 Web Worker
const worker = new Worker('worker.js');
worker.postMessage({ data: 'some data' });
javascript// 监听 message 事件
window.addEventListener('message', (event) => {
// 安全检查:验证消息来源
if (event.origin !== 'https://example.com') return;
console.log('Received message:', event.data);
});
// Web Worker 中接收消息
self.onmessage = (event) => {
console.log('Worker received:', event.data);
};
data: 要传递的数据(可以是字符串、对象等)。targetOrigin: 指定接收方的源('https://example.com' 或 '*' 表示不限制)。这是安全机制,防止数据泄露到不可信的域名。event 对象的属性:
event.data: 接收到的数据。event.origin: 发送方的源(用于验证身份)。event.source: 发送方的窗口引用(可用于回复消息)。| 特性 | postMessage (JavaScript API) | HTTP POST 方法 |
|---|---|---|
| 用途 | 跨文档/跨线程通信 | 客户端向服务器提交数据 |
| 协议层 | 浏览器 API,非 HTTP 协议 | HTTP 协议的一部分 |
| 数据传输方向 | 窗口 ↔ 窗口、主线程 ↔ Worker | 客户端 → 服务器 |
| 安全性 | 依赖 origin 验证 | 依赖 HTTPS、CORS 等 |
event.origin:防止恶意网站伪造消息。* 作为 targetOrigin:除非明确需要广播到所有域名。html<!-- 父页面 (https://parent.com) -->
<iframe id="child" src="https://child.com"></iframe>
<script>
const iframe = document.getElementById('child');
iframe.contentWindow.postMessage('Hello child!', 'https://child.com');
</script>
<!-- 子页面 (https://child.com) -->
<script>
window.addEventListener('message', (event) => {
if (event.origin !== 'https://parent.com') return;
console.log('Child received:', event.data);
event.source.postMessage('Hi parent!', event.origin); // 回复
});
</script>
下面将设计一个基于原生 JavaScript 的 postMessage 通信插件,它具有以下特点:
javascript/**
* CrossWindowMessenger - 基于 postMessage 的跨窗口/跨域通信插件
* @param {Object} options 配置选项
*/
function CrossWindowMessenger(options = {}) {
// 默认配置
const defaults = {
targetOrigin: window.location.origin, // 默认只允许同源通信
verifyOrigin: true, // 是否验证消息来源
namespace: 'default', // 命名空间,用于区分不同通信频道
debug: false // 调试模式
};
// 合并配置
this.settings = { ...defaults, ...options };
this.messageHandlers = {};
this.targetWindows = new Set();
// 初始化消息监听
this._initListener();
}
/**
* 初始化消息监听
*/
CrossWindowMessenger.prototype._initListener = function() {
window.addEventListener('message', (event) => {
// 安全验证
if (this.settings.verifyOrigin && event.origin !== this.settings.targetOrigin) {
this._log(`Message from unauthorized origin: ${event.origin}`);
return;
}
const { data } = event;
// 检查是否是本插件的消息
if (!data || data.namespace !== this.settings.namespace) {
return;
}
this._log(`Received message:`, data);
// 处理消息
if (data.type === 'request' && this.messageHandlers[data.messageId]) {
// 处理请求并返回响应
const handler = this.messageHandlers[data.messageId];
const response = handler(data.payload);
// 发送响应
event.source.postMessage({
namespace: this.settings.namespace,
type: 'response',
messageId: data.messageId,
payload: response
}, event.origin);
} else if (data.type === 'response' && this.messageHandlers[data.messageId]) {
// 处理响应
const handler = this.messageHandlers[data.messageId];
handler(data.payload);
// 清理一次性处理器
delete this.messageHandlers[data.messageId];
} else if (data.type === 'event') {
// 处理事件
if (this.eventHandlers && this.eventHandlers[data.eventName]) {
this.eventHandlers[data.eventName].forEach(handler => {
handler(data.payload);
});
}
}
});
};
/**
* 添加目标窗口
* @param {Window} targetWindow 目标窗口对象
*/
CrossWindowMessenger.prototype.addTargetWindow = function(targetWindow) {
this.targetWindows.add(targetWindow);
return this;
};
/**
* 移除目标窗口
* @param {Window} targetWindow 目标窗口对象
*/
CrossWindowMessenger.prototype.removeTargetWindow = function(targetWindow) {
this.targetWindows.delete(targetWindow);
return this;
};
/**
* 发送消息到所有目标窗口
* @param {String} type 消息类型
* @param {*} payload 消息内容
*/
CrossWindowMessenger.prototype._sendToAll = function(type, payload) {
this.targetWindows.forEach(target => {
target.postMessage({
namespace: this.settings.namespace,
type: type,
payload: payload
}, this.settings.targetOrigin);
});
};
/**
* 发送请求并等待响应
* @param {*} payload 请求内容
* @param {Function} callback 回调函数
* @returns {Promise} 返回Promise
*/
CrossWindowMessenger.prototype.request = function(payload, callback) {
return new Promise((resolve) => {
const messageId = this._generateId();
// 注册一次性处理器
this.messageHandlers[messageId] = (response) => {
if (callback) callback(response);
resolve(response);
};
// 发送请求
this._sendToAll('request', {
messageId: messageId,
payload: payload
});
});
};
/**
* 发送事件到所有目标窗口
* @param {String} eventName 事件名称
* @param {*} payload 事件内容
*/
CrossWindowMessenger.prototype.emit = function(eventName, payload) {
this._sendToAll('event', {
eventName: eventName,
payload: payload
});
};
/**
* 监听事件
* @param {String} eventName 事件名称
* @param {Function} handler 处理函数
*/
CrossWindowMessenger.prototype.on = function(eventName, handler) {
if (!this.eventHandlers) {
this.eventHandlers = {};
}
if (!this.eventHandlers[eventName]) {
this.eventHandlers[eventName] = [];
}
this.eventHandlers[eventName].push(handler);
return this;
};
/**
* 取消监听事件
* @param {String} eventName 事件名称
* @param {Function} handler 处理函数
*/
CrossWindowMessenger.prototype.off = function(eventName, handler) {
if (!this.eventHandlers || !this.eventHandlers[eventName]) {
return this;
}
if (handler) {
const index = this.eventHandlers[eventName].indexOf(handler);
if (index !== -1) {
this.eventHandlers[eventName].splice(index, 1);
}
} else {
delete this.eventHandlers[eventName];
}
return this;
};
/**
* 生成唯一ID
*/
CrossWindowMessenger.prototype._generateId = function() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
};
/**
* 调试日志
*/
CrossWindowMessenger.prototype._log = function(...args) {
if (this.settings.debug) {
console.log('[CrossWindowMessenger]', ...args);
}
};
// 导出插件
if (typeof module !== 'undefined' && module.exports) {
module.exports = CrossWindowMessenger;
} else if (typeof define === 'function' && define.amd) {
define([], function() { return CrossWindowMessenger; });
} else {
window.CrossWindowMessenger = CrossWindowMessenger;
}
html<!-- 父页面 -->
<iframe id="childFrame" src="https://child-domain.com"></iframe>
<script>
const messenger = new CrossWindowMessenger({
targetOrigin: 'https://child-domain.com',
namespace: 'myApp',
debug: true
});
// 添加目标窗口
const iframe = document.getElementById('childFrame');
messenger.addTargetWindow(iframe.contentWindow);
// 发送请求并等待响应
messenger.request({ action: 'getUserInfo' }, (response) => {
console.log('Received response:', response);
});
// 发送事件
messenger.emit('userLoggedIn', { userId: 123 });
// 监听事件
messenger.on('dataUpdated', (data) => {
console.log('Data updated:', data);
});
</script>
<!-- 子页面 (https://child-domain.com) -->
<script>
const childMessenger = new CrossWindowMessenger({
targetOrigin: 'https://parent-domain.com',
namespace: 'myApp'
});
// 添加父窗口为目标
childMessenger.addTargetWindow(window.parent);
// 监听请求
childMessenger.on('request', (payload, respond) => {
if (payload.action === 'getUserInfo') {
respond({ name: 'John Doe', age: 30 });
}
});
// 发送事件到父窗口
childMessenger.emit('dataUpdated', { newData: 'some value' });
</script>
javascript// 使用Promise
messenger.request({ action: 'getData' })
.then(response => {
console.log('Got response:', response);
})
.catch(error => {
console.error('Request failed:', error);
});
安全性:
targetOrigin)易用性:
request, emit, on, off)扩展性:
兼容性:
javascript// 向多个iframe广播消息
const iframes = document.querySelectorAll('iframe');
const messenger = new CrossWindowMessenger({
targetOrigin: 'https://target-domain.com'
});
iframes.forEach(iframe => {
messenger.addTargetWindow(iframe.contentWindow);
});
// 向所有iframe发送消息
messenger.emit('refreshData', { force: true });
javascript// 添加自定义消息处理器
messenger.on('customEvent', (data) => {
console.log('Custom event received:', data);
// 处理自定义逻辑
});
// 发送自定义消息
messenger.emit('customEvent', { customData: 'value' });
javascript// 添加超时功能
function requestWithTimeout(payload, timeout = 5000) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error('Request timeout'));
}, timeout);
messenger.request(payload)
.then(response => {
clearTimeout(timer);
resolve(response);
})
.catch(reject);
});
}
这个插件设计提供了完整的跨窗口/跨域通信解决方案,同时保持了简单性和安全性。你可以根据具体需求进一步扩展或修改它。
本文作者:wucc
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-SA 许可协议。转载请注明出处!