前言
在某些场景下,为了用户打开的网页窗口之间能够互相感知,我们需要使用各种方式让同源、非同源的 窗口之间能够实时、高效的共享数据、互相通信。
- 打开某平台的多个页面,登录其中一个后,通知其他页面自动更新登录状态
- 接入第三方登录,在新窗口登录操作成功后,通知页面用户的认证数据
- 前篇文章的跨窗口动画 ……
随着前端的发展和浏览器的更新,在不借助后端(轮询、websocket、sse)的情况下,前端自身也有较完善的方案实现高效、便捷的通信。
本文,我们一起学习纯前端跨窗口通信的几种常见方式:localStorage
、postMessage
、Broadcast Channel
、sharedWorker
,了解它们的相关知识与应用。
如果有收获还望 点赞+收藏 🌹
简介
API | 同源限制 | 优点 | 缺点 | 原理 | 兼容性 |
---|---|---|---|---|---|
localStorage | 受限制 | 便捷、兼容性高 | 读写转换可能有问题 | 本地存储 &StorageEvent |
主流支持 |
postMessage | 不受限制 | 安全的跨源通信 | 只能点对点的传输数据 | 窗口引用 &MessageEvent |
主流支持 |
BroadcastChannel | 受限制 | 支持多对多的广播 | 单点推送需要手动处理 | 发布-订阅模式 | 主流支持 |
SharedWorker | 受限制 | 性能强、不消耗主进程 | 使用较复杂、兼容性差 | web worker 线程 |
支持较差 |
兼容
出于兼容性考虑,我们可以在代码中判断浏览器是否支持当前方案 并采用降级方案
let broadcastChannel;
if (window.BroadcastChannel) {
broadcastChannel = new BroadcastChannel('test');
}
if (broadcastChannel) {
// ...
} else {
window.onstorage = (e) => {
// ...
}
}
共享
保持页面之间数据同步的方式各有不同:
localStorage
使用的同一个storage
对象,数据自带同步SharedWorker
可以把数据存储到共享worker
中,在通知时发送BroadcastChannel
&postMessage
只能保存在各自的窗口中,每份数据都是独立的
通信
它们的通信方式都继承自Event
事件:
localStorage
页面中storage
对象被修改时会触发StorageEvent
事件postMessage
、Broadcast Channel
、sharedWorker
的通信借助了MessageEvent
事件
API
– localStorage
localStorage
允许你访问一个 Storage
对象,用于访问、修改特定域名下的本地存储数据。
localStorage.getItem("data-time");
localStorage.setItem("data-time", Date.now());
localStorage.clear("data-time");
存储数据 受同源策略限制,即只有相同域名、协议和端口的页面才能相互访问各自的数据
当存储区域(localStorage
)发生变化时,将在文档的 Window
对象上触发 storage
事件。
StorageEvent
// pageA
window.addEventListener('storage', (event) => {
console.log(event);
// do something...
});
storage
事件的属性主要有:
- key,代表被修改的键名
- url,代表发生改变的对象所在文档的 URL 地址
- oldValue、newValue,代表变化前后的值
- storageArea,代表被操作的
storage
对象(包含其他key
的值)
利用 localStorage
的本地存储——共享数据,通过监听 storage
事件触发回调——通知更新。
// pageB
localStorage.setItem("data-time", Date.now());
localStorage.clear("data-time");
// pageA
// *Console:StorageEvent {...}*
// *Console:StorageEvent {...}*
– postMessage
window.postMessage
提供了一种不同窗口之间 安全通信 的方法,它允许来自一个窗口的文档发送消息到另一个窗口的文档,即使它们不是同源的。
otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow
是对其他窗口的引用:
- 例如
iframe
的contentWindow
属性 - 返回的窗口对象,
const opener = window.open(xxURL);
- 嵌套的窗口对象,
window.parent
或者是window.frames
等
postMessage
方法接受两个参数:
- message,要发送的消息,它将被序列化
- targetOrigin,字符串”*”(表示无限制)或者一个 URI,用于指定哪些窗口能接收到消息事件
postMessage
方法会分发一个 MessageEvent
事件,接收消息的窗口可以根据需要自由处理此事件。
// otherWindow
window.addEventListener("message", (event) => {
console.log(event);
// do something...
});
MessageEvent
message
事件的属性有:
- data,从其他 window 中传递过来的消息对象
- origin,调用
postMessage
时消息发送方窗口的origin
,例如https://example.org
、http://example.com:8080
- source,对发送消息的窗口对象的引用; 你可以使用此来在具有不同
origin
的两个窗口之间建立双向通信
如果你想尽可能的避免安全问题,请始终提供一个有确切值的
targetOrigin
,并且在接收信息时使用origin
和source
属性来检查消息的发送者的身份
– Broadcast Channel
BroadcastChannel API
提供了简单的方法来 创建、连接频道,它允许同源的不同浏览器窗口,Tab 页,frame 或者 iframe 下的不同文档之间通过频道进行 消息广播 和 接收。
// 创建、连接频道
const broadcastChannel = new BroadcastChannel("cross-window-broadcast");
// 广播信息
broadcastChannel.postMessage(data);
// 关闭频道
broadcastChannel.close();
BroadcastChannel()
构造函数接受一个参数,创建一个BroadcastChannel
对象:
- channelName,表示频道名称的字符串
BroadcastChannel
对象与指定频道相连,提供两个方法:
- postMessage,向所有监听了相同频道的
BroadcastChannel
对象发送一条消息,消息内容可以是任意类型的数据 - close,关闭频道对象,不再接收新的消息
// 处理信息
broadcastChannel.onmessage = (event) => handleChannelMessage(event);
broadcastChannel.addEventListener('message', (event) => handleChannelMessage(event));
当频道收到一条消息时,会在关联的 BroadcastChannel
对象上触发 MessageEvent
事件。
message
事件的属性有:
- data,由消息发送者发送的数据
- origin,表示消息发送者来源的字符串
- source,消息事件源,可以是一个用于表示消息发送者的 WindowProxy (en-US)、
MessagePort
或ServiceWorker
对象 - ports,与发送消息(通过频道发送消息或向 SharedWorker 发送消息)的频道相关联的
MessagePort
对象的数组。
– sharedWorker
SharedWorker
是 HTML5 新增的 Web Workers
类型之一, 它允许多个同源的浏览上下文(比如 窗口、iframe、不同的Tab页)共享一个 worker 实例。
SharedWorker
可以用于在多个页面之间 共享数据 和 执行复杂计算,是一个强大的多线程工具。
其中 message
事件的属性与 BroadcastChannel
相同就不介绍了。
// html -> script
// 创建一个共享进程对象
const myWorker = new SharedWorker("./sharedWorked.js");
// 手动启动端口
myWorker.port.start();
// 监听信息的发送
myWorker.port.onmessage = (event) => {
console.log('event:', event);
handleWorkerMessage(event.data);
}
function sendWorkerMessage(data) {
myWorker.port.postMessage(data);
}
function handleWorkerMessage(data) {
console.log(data);
// do something...
};
// sharedWorker.js
onconnect = function (event) {
const port = event.ports[0];
port.addEventListener("message", (event) {
const result = "Result: " + event.data[0] * event.data[1];
port.postMessage({result});
});
port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter.
};
总结
本文介绍了几种常见的前端跨窗口通信方法和它们的优缺点,并进行了基础实践。
它们的兼容性、性能、便捷性和通信方式等各方面各有不同,开发时根据场景需要选择更合适的方案。
结语 🎉
不要光看不实践哦,希望能对你有所帮助~
持续更新前端知识,脚踏实地不水文,真的不关注一下吗~
才疏学浅,如有问题或建议还望指教!
原文链接:https://juejin.cn/post/7354589998313259044 作者:西维