值得了解的「前端跨窗口通信」常用方法

713 阅读6分钟

前言

在某些场景下,为了用户打开的网页窗口之间能够互相感知,我们需要使用各种方式让同源、非同源的 窗口之间能够实时、高效的共享数据、互相通信

  • 打开某平台的多个页面,登录其中一个后,通知其他页面自动更新登录状态
  • 接入第三方登录,在新窗口登录操作成功后,通知页面用户的认证数据
  • 前篇文章的跨窗口动画 ......
image.png

随着前端的发展和浏览器的更新,在不借助后端(轮询、websocket、sse)的情况下,前端自身也有较完善的方案实现高效、便捷的通信。

本文,我们一起学习纯前端跨窗口通信的几种常见方式:localStoragepostMessageBroadcast ChannelsharedWorker ,了解它们的相关知识与应用。

如果有收获还望 点赞+收藏 🌹

简介

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 事件
  • postMessageBroadcast ChannelsharedWorker的通信借助了MessageEvent事件
image.png

API

跨页面通信.png

- 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 事件的属性主要有:

  1. key,代表被修改的键名
  2. url,代表发生改变的对象所在文档的 URL 地址
  3. oldValuenewValue,代表变化前后的值
  4. storageArea,代表被操作的storage对象(包含其他key的值)
image.png

利用 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 是对其他窗口的引用:

  1. 例如 iframecontentWindow 属性
  2. 返回的窗口对象const opener = window.open(xxURL);
  3. 嵌套的窗口对象window.parent或者是window.frames

postMessage 方法接受两个参数:

  1. message,要发送的消息,它将被序列化
  2. targetOrigin,字符串"*"(表示无限制)或者一个 URI,用于指定哪些窗口能接收到消息事件

postMessage 方法会分发一个 MessageEvent 事件,接收消息的窗口可以根据需要自由处理此事件。

// otherWindow
window.addEventListener("message", (event) => { 
    console.log(event);
    // do something... 
});

MessageEvent

message 事件的属性有:

  1. data,从其他 window 中传递过来的消息对象
  2. origin,调用 postMessage 时消息发送方窗口的 origin,例如 https://example.orghttp://example.com:8080
  3. source,对发送消息的窗口对象的引用; 你可以使用此来在具有不同 origin 的两个窗口之间建立双向通信
ezgif-7-29f1c4e5d3.gif

如果你想尽可能的避免安全问题,请始终提供一个有确切值的targetOrigin,并且在接收信息时使用 originsource 属性来检查消息的发送者的身份

- Broadcast Channel

image.png

BroadcastChannel API 提供了简单的方法来 创建、连接频道,它允许同源的不同浏览器窗口,Tab 页,frame 或者 iframe 下的不同文档之间通过频道进行 消息广播接收

// 创建、连接频道
const broadcastChannel = new BroadcastChannel("cross-window-broadcast");
// 广播信息
broadcastChannel.postMessage(data);
// 关闭频道
broadcastChannel.close();

BroadcastChannel() 构造函数接受一个参数,创建一个BroadcastChannel对象:

  1. channelName,表示频道名称的字符串

BroadcastChannel 对象与指定频道相连,提供两个方法:

  1. postMessage,向所有监听了相同频道的 BroadcastChannel 对象发送一条消息,消息内容可以是任意类型的数据
  2. close,关闭频道对象,不再接收新的消息
// 处理信息
broadcastChannel.onmessage = (event) => handleChannelMessage(event);
broadcastChannel.addEventListener('message', (event) => handleChannelMessage(event));

当频道收到一条消息时,会在关联的 BroadcastChannel 对象上触发 MessageEvent 事件。

message 事件的属性有:

  1. data,由消息发送者发送的数据
  2. origin,表示消息发送者来源的字符串
  3. source,消息事件源,可以是一个用于表示消息发送者的 WindowProxy (en-US)MessagePort 或 ServiceWorker 对象
  4. ports,与发送消息(通过频道发送消息或向 SharedWorker 发送消息)的频道相关联的 MessagePort 对象的数组。
ezgif-1-71a68964ac.gif

image.png

- sharedWorker

image.png

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.
};
ezgif-3-46edb6583f.gif

总结

本文介绍了几种常见的前端跨窗口通信方法和它们的优缺点,并进行了基础实践。

它们的兼容性、性能、便捷性和通信方式等各方面各有不同,开发时根据场景需要选择更合适的方案。

结语 🎉

不要光看不实践哦,希望能对你有所帮助~

持续更新前端知识,脚踏实地不水文,真的不关注一下吗~

才疏学浅,如有问题或建议还望指教!