浅析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
