浅析postMessage
前言
在前端开发中,有时候我们需要在两个页面间进行数据交换。如果两个页面属于同一个源,我们有很多办法可以实现这一目标。但是,当两个页面属于不同的源时,办法就很有限了。本文将介绍其中的一种方法,即使用postMessage
在不同源的两个页面间进行数据交换,当然该方法也适用于同源的页面间。
很多接口(interface)都提供了postMessage
方法,比如Window
,MessagPort
,Worker
,Client
,ServiceWorker
,BroadcastChannel
等。首先要做的事情是分清楚不同本文将结合在不同源的两个页面间进行数据交换的场景,介绍postMessage
的作用和应用场景,Window: postMessage()
和MessagePort: postMessage()
方法。
Window: postMessage()
window.postMessage()
方法可以安全地在不同源的window
对象间进行通信。当然这有两个前提,
- 消息发送方需要拿到接收方的
window
对象, - 消息接收方需要监听
message
事件
有两种方式可以满足前提1,分别是调用window.open()
和使用<iframe>
标签,因此也限制了window.postMessage()
方法只能在这两种场景下使用。
window.open()
返回被打开页面的window
对象,被打开的页面则可以通过window.opener
访问调用了window.open()
方法的页面window
对象。主frame可以通过
iframe.contentWindow
访问iframe
内部的window
对象,iframe
内部则可以通过window.parent
访问主frame的window
对象。消息接收方可以通过收到消息中的
source
属性获得发送方的window
对象。
API
发送方API
1 | postMessage(message) |
message
参数很好理解,就是发送的消息,可以传递能够被结构化克隆[3]的对象,比如js中的大多数类型。targetOrigin
参数指定接收方的origin
,只有和实际的接收方origin
(包括协议、域名和端口)相同时,接收方才能收到该消息。默认取当前页面的origin
,可以设置为*
,表示所有源都可以收到该消息。transfer
参数表示将一组可转移对象(Transferable objects)[4]的控制权交给接收方,用于共享内存或者交换MessagePort
对象。options
参数是一个对象,包含targetOrigin
和transfer
属性。
接收方收到的参数
接收方可以通过监听message
事件来接收消息。
1 | window.addEventListener( |
event
包含下列属性:
data
:接收到的消息,对应发送方的message
参数。origin
:消息发送方的origin
,这里的目的其实是和发送方做相互校验。source
:消息发送方的window
对象,可以用该对象建立双向通信通道。
🌰
发送消息
1
2var o = document.getElementsByTagName('iframe')[0];
o.contentWindow.postMessage('Hello world', 'https://b.example.org/');接收消息
1
2
3
4
5
6
7
8
9
10
11window.addEventListener('message', receiver, false);
function receiver(e) {
if (e.origin == 'https://example.com') {
if (e.data == 'Hello world') {
e.source.postMessage('Hello', e.origin);
} else {
alert(e.data);
}
}
}
MessagePort:postMessage()
Channel Messaging API目的是让两个不同上下文的脚本可以直接建立通道,进行双向通信。MessagePort
则是使用该API建立通道后的两侧端点。
API
建立通道(MessageChannel)
1 | const channel = new MessageChannel(); |
channel
有两个属性,port1
和port2
,分别是通道的两侧端点即MessagePort
对象。具体的收发消息需要使用这两个端点来完成。
收发消息(MessagePort)
1 | // 发送消息 |
MessagePort
是一个可转移对象。
postMessage
的参数含义和window.postMessage
一样,在此不再赘述,不一样的地方是不需要指定targetOrigin
参数了。接收消息的方式也和接收
window.postMessage
消息的方式一样,只是调用方由window
对象变成了MessagePort
对象。有一点不一样的是,如果使用addEventListener("message", (event) => {});
方式来监听消息的话,需要手动执行start()
方法。event
包含下列属性:data
:接收到的消息,对应发送方的message
参数。origin
:消息发送方的origin
,实践中不同源场景下两侧端点收到的消息origin
都为空。source
:消息发送方,可以是WindowProxy, MessagePort或者ServiceWorker,实践中不同源场景下两侧端点收到的消息source
都为空。lastEventId
:消息的序号,实践中不同源场景下两侧端点收到的消息lastEventId
都为空。ports
:与消息一起发送的所有MessagePort
对象8,实践中不同源场景下两侧端点收到的消息ports
都为空数组。
start()
: 开始接收消息,在调用该方法之前收到的消息并不会丢弃,只是不执行message
的回调函数。等start()
执行后,之前收到的消息会依次调用已注册的回调函数。close()
: 关闭接收消息。
到目前为止,创建的channel
和对应的port1
和port2
都还是在同一个上下文中。要实现在不同上下文的脚本中直接建立通道,需要借助可以转移对象控制权的方法,比如前文所述的Window: postMessage()
。
🌰
发送消息
1
2
3var channel = new MessageChannel();
otherWindow.postMessage('hello', 'https://example.com', [channel.port2]);
channel.port1.postMessage('hello');接收消息
1
2
3
4
5channel.port2.onmessage = handleMessage;
function handleMessage(event) {
// message is in event.data
// ...
}
refences
- https://www.google.com/search?q=postmessage+mdn&newwindow=1&sca_esv=d79442c60ff0c8fa&sxsrf=ADLYWIK6h__HzcQYgHwwAuYeqfWZyZftLw%3A1735269517122&source=hp&ei=jRxuZ8OrBajk2roP646MsQY&iflsig=AL9hbdgAAAAAZ24qnZBmMSYyd9SKfIRjjIjC4h2jXTxI&ved=0ahUKEwiDpp_Z_saKAxUoslYBHWsHI2YQ4dUDCBg&uact=5&oq=postmessage+mdn&gs_lp=Egdnd3Mtd2l6Ig9wb3N0bWVzc2FnZSBtZG4yChAjGIAEGCcYigUyBhAAGBYYHjIGEAAYFhgeMgsQABiABBiGAxiKBTILEAAYgAQYhgMYigUyCxAAGIAEGIYDGIoFSIAhUABY9R9wBXgAkAEAmAGCAaAB7AmqAQQxLjEwuAEDyAEA-AEBmAIQoAKNCsICBBAjGCfCAgsQABiABBiRAhiKBcICCxAuGIAEGLEDGIMBwgIIEAAYgAQYsQPCAgsQABiABBixAxiDAcICDhAAGIAEGLEDGIMBGIoFwgIKEAAYgAQYQxiKBcICDRAAGIAEGLEDGEMYigXCAgsQABiABBixAxjJA8ICDhAuGIAEGNEDGJIDGMcBwgIFEC4YgATCAgUQABiABMICCxAuGIAEGLEDGNQCwgIIEAAYgAQYywHCAgkQABgWGMcDGB6YAwCSBwQ1LjExoAelUw&sclient=gws-wiz
- https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
- https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
- https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects
- https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API
- https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API/Using_channel_messaging
- https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
- https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/message_event
- https://html.spec.whatwg.org/multipage/web-messaging.html