阅读视图

发现新文章,点击刷新页面。

前端跨页面通讯终极指南②:BroadcastChannel 用法全解析

前言

上一篇介绍了PostMessage跨页面通讯的方式。有没有一种更简洁的方式,兄弟页面也能像父子页面一样通讯。今天就介绍一个更高效、更简洁的方案——BroadcastChannel API,它能轻松搞定父子、子父、兄弟页面间的通讯。

1. BroadcastChannel是什么?

BroadcastChannel 接口表示给定的任何浏览上下文都可以订阅的命名频道。它允许同源的不同浏览器窗口、标签页、frame 或者 iframe 下的不同文档之间相互通信。消息通过 message 事件进行广播,该事件在侦听该频道的所有 BroadcastChannel 对象上触发,发送消息的对象除外。

简单来说,它就像一个“无线电台”,多个页面只要订阅了同一个“频道”(指定相同的频道名称),就能接收该频道发送的所有消息,实现数据的双向流转。

需要注意,BroadcastChannel仅支持同源页面,兼容性略低,需要搭配其他方案作为降级处理。

2. 如何使用

BroadcastChannel的使用流程如下:

  1. 创建频道
  2. 订阅消息
  3. 发送消息
  4. 关闭订阅

2.1 创建频道(主题)

通过 new BroadcastChannel('channel-name')创建一个“频道”(相当于发布-订阅中的“主题”)。所有加入同一频道的上下文,共享同一个通信通道。

// 1. 创建/订阅指定名称的频道(关键:多页面频道名必须一致)
const channel = new BroadcastChannel('my-channel');

2.2 订阅消息(监听)

通过监听 message事件订阅该频道的消息(相当于订阅者注册回调):

channel.onmessage = (e) => {
  console.log('收到消息:', e.data); // e.data 就是发送的消息内容
  // 根据消息类型执行对应逻辑
  if (e.data.type === 'refresh') {
    // 执行刷新操作
  }
};

2.3 发布消息(发送)

通过 postMessage()向频道发送消息(相当于发布者触发发布):

channel.postMessage({
  type: 'refresh',
  data: { id: 123 }
});

2.4 退订(关闭)

通过 close()方法离开频道(可选,浏览器通常会在上下文销毁时自动清理):

window.addEventListener('beforeunload', () => {
  channel.close();
});
// 1. 创建/订阅指定名称的频道(关键:多页面频道名必须一致)
const channel = new BroadcastChannel('my-channel');

// 2. 接收消息
channel.onmessage = (e) => {
  console.log('收到消息:', e.data); // e.data 就是发送的消息内容
  // 根据消息类型执行对应逻辑
  if (e.data.type === 'refresh') {
    // 执行刷新操作
  }
};

// 3. 发送消息(支持字符串、对象等多种数据类型)
channel.postMessage({
  type: 'refresh',
  data: { id: 123 }
});

// 4. 关闭频道(页面卸载时调用,避免内存泄漏)
window.addEventListener('beforeunload', () => {
  channel.close();
});

3、实践场景

下面我们针对前端最常见的父子页面(父窗口打开子窗口)、子父页面(子窗口向父窗口反馈)、兄弟页面(同一父页面打开的多个子窗口)三种场景,分别给出具体的实现代码。

3.1 父子通讯

父页面点击“刷新子页面”按钮,子页面接收到指令后刷新数据。

父页面代码(打开子窗口并发送消息):

// 1. 创建频道
const parentChannel = new BroadcastChannel('parent-child-channel');

// 2. 点击按钮向子窗口发送消息
document.getElementById('refreshChildBtn').addEventListener('click', () => {
  parentChannel.postMessage({
    type: 'refresh-data',
    message: '父页面指令:请刷新数据'
  });
});

// 3. 页面卸载时关闭频道
window.addEventListener('beforeunload', () => {
  parentChannel.close();
});

子页面代码(接收父页面消息并执行操作):

// 1. 订阅同一个频道(频道名必须和父页面一致)
const childChannel = new BroadcastChannel('parent-child-channel');

// 2. 接收父页面消息
childChannel.onmessage = (e) => {
  const { type, message } = e.data;
  if (type === 'refresh-data') {
    // 显示接收的消息
    document.getElementById('message').textContent = message;
    // 执行刷新数据的逻辑
    refreshData();
  }
};

// 刷新数据的核心函数
function refreshData() {
  // 模拟请求接口刷新数据
  console.log('子页面正在刷新数据...');
  // 这里写具体的刷新逻辑
}

// 3. 页面卸载时关闭频道
window.addEventListener('beforeunload', () => {
  childChannel.close();
});

3.2 子父通讯(子窗口向父窗口反馈结果)

需求:子页面完成表单提交后,向父页面发送“提交成功”的消息,父页面接收到后关闭子窗口并刷新自身数据。

子页面代码(提交表单后发送消息):

// 1. 订阅频道(和父页面保持一致)
const childChannel = new BroadcastChannel('child-parent-channel');

// 2. 表单提交逻辑
document.getElementById('submitForm').addEventListener('submit', async (e) => {
  e.preventDefault();
  try {
    // 模拟表单提交接口请求
    await submitFormData();
    // 提交成功后向父页面发送消息
    childChannel.postMessage({
      type: 'submit-success',
      data: { formId: 456, status: 'success' }
    });
    // 延迟关闭子窗口,确保消息发送完成
    setTimeout(() => {
      window.close();
    }, 300);
  } catch (error) {
    console.error('提交失败:', error);
  }
});

// 3. 页面卸载时关闭频道
window.addEventListener('beforeunload', () => {
  childChannel.close();
});

父页面代码(接收子页面消息并执行操作):

// 1. 创建频道
const parentChannel = new BroadcastChannel('child-parent-channel');

// 2. 接收子页面消息
parentChannel.onmessage = (e) => {
  const { type, data } = e.data;
  if (type === 'submit-success') {
    console.log('子页面表单提交成功:', data);
    // 执行父页面刷新逻辑
    parentRefreshData();
    // (可选)关闭子窗口(如果子窗口未自行关闭)
    // childWindow.close();
  }
};

// 父页面刷新数据函数
function parentRefreshData() {
  console.log('父页面正在刷新数据...');
  // 具体刷新逻辑
}

// 3. 页面卸载时关闭频道
window.addEventListener('beforeunload', () => {
  parentChannel.close();
});

3.3 兄弟通讯(多个子窗口间同步状态)

需求:父页面打开两个子窗口A和B,当子窗口A修改数据后,子窗口B实时同步更新数据。

子窗口A代码(修改数据后发送消息):

// 1. 订阅兄弟通讯频道
const brotherChannel = new BroadcastChannel('brother-channel');

// 2. 模拟修改数据操作
document.getElementById('updateDataBtn').addEventListener('click', () => {
  const newData = {
    id: 789,
    content: '子窗口A修改后的新内容',
    updateTime: new Date().toLocaleString()
  };
  // 保存修改后的数据
  saveData(newData);
  // 向频道发送消息,通知其他兄弟窗口
  brotherChannel.postMessage({
    type: 'data-updated',
    newData: newData
  });
});

// 3. 页面卸载时关闭频道
window.addEventListener('beforeunload', () => {
  brotherChannel.close();
});

子窗口B代码(接收消息并同步数据):

// 1. 订阅同一个兄弟通讯频道
const brotherChannel = new BroadcastChannel('brother-channel');

// 2. 接收子窗口A发送的消息
brotherChannel.onmessage = (e) => {
  const { type, newData } = e.data;
  if (type === 'data-updated') {
    console.log('收到子窗口A的更新消息:', newData);
    // 同步更新页面数据展示
    document.getElementById('dataContent').textContent = newData.content;
    document.getElementById('updateTime').textContent = newData.updateTime;
  }
};

// 3. 页面卸载时关闭频道
window.addEventListener('beforeunload', () => {
  brotherChannel.close();
});

接收输入如下:

image.png

4. 总结

最后总结一下:对比传统的postMessage跨页面通讯方案,BroadcastChannel兄弟页面无需转发,几行代码就能轻松实现通讯。

前端跨页面通讯终极指南①:postMessage 用法全解析

前言

公司后台项目微前端是使用iframe方式,跨页面通讯postMessage就需要我们必须掌握。比如,弹窗页与父页面的数据同步、多个标签页间的状态共享、嵌入的iframe与宿主页面的交互等。

本文将从基础原理到实战场景,全面解析 postMessage 的用法,帮你轻松搞定各类跨页面通讯需求。

先看一张总结图,了解通讯的几种场景:

image.png

1. 什么是postMessage

postMessage 是用于在不同源的窗口、iframe、Worker之间安全地传递数据。打破了浏览器的“同源策略”限制,让跨源页面之间能够实现数据传递和事件通信。

postMessage 的使用逻辑非常简单,分为“发送数据”和“接收数据”两个步骤,本质是基于“消息发布-订阅”模式。

1.1 发送数据:targetWindow.postMessage()

发送数据的操作由“发送方窗口”调用 postMessage 方法完成,该方法挂载在窗口对象(window)上,语法如下:

targetWindow.postMessage(message, targetOrigin, [transfer]);

参数的含义和使用要点:

  1. targetWindow(必选) :接收消息的目标窗口对象,即“谁要接收这个消息”。常见的获取方式有: iframe的contentWindow:document.getElementById('iframeId').contentWindow(父页向子页发消息);
  2. window.opener:通过window.open()打开的新窗口,其内部通过opener获取父窗口(子页向父页发消息);
  3. window.parent:iframe内部通过parent获取父窗口(子页向父页发消息);
  4. message(必选) :要发送的数据,可以是字符串、数字、对象、数组等几乎所有类型。但需要注意: 数据会被隐式序列化为JSON格式传递,接收方需要自行解析(部分浏览器会自动反序列化,但建议显式处理以兼容);
  5. 避免发送过大的数据(如超过10MB),可能导致性能问题或传输失败。
  6. targetOrigin(必选) :目标窗口的“源”(协议+域名+端口),用于安全校验,即“只有该源的窗口才能接收消息”。取值规则: 具体源:如'https://www.example.com:8080',仅该源的窗口能接收;
  7. 通配符'*':允许所有源接收消息(极度危险,仅开发测试时临时使用);
  8. 空字符串'':仅适用于发送给file://协议的窗口(实际开发中极少用)。
  9. transfer(可选) :是一个包含可转移对象的数组,这些对象的所有权会从发送方转移到接收方,发送方后续无法再使用这些对象(如ArrayBuffer)。该参数使用场景较少,一般无需关注。

1.2 接收数据:监听 message 事件

接收数据的窗口需要监听自身的message事件,当有其他窗口通过postMessage发送消息时,该事件会被触发。语法如下:

window.addEventListener('message', (event) => {
  // 处理接收的消息
}, false);

核心是解析事件对象event的三个关键属性:

  1. event.data:发送方传递的消息数据(即postMessage的第一个参数);
  2. event.origin:发送消息的窗口的“源”(协议+域名+端口),用于校验发送方身份;
  3. event.source:发送消息的窗口对象,可用于向发送方回传数据。

接收方必须通过event.origin校验发送方的合法性,避免接收恶意源发送的消息,这是与targetOrigin对应的双重安全保障。

2. 实战案例:同页面iframe通讯

父页面嵌入iframe,两者需要实现数据交互(如父页向子页传用户信息,子页向父页传操作结果)。

2.1 父->子

发送端(父页面):

// 发送到指定 iframe
iframe1.contentWindow.postMessage({
    from: 'Parent (父页面)',
    message: '消息内容'
}, '*');

接收端(子页面):

// 在 Vue 组件中监听消息
window.addEventListener('message', function(event) {
});

接收的数据:

image.png

2.2 子->父

发送端(子页面):

// 从子页面发送消息到父页面
window.parent.postMessage({
    target: 'parent',
    from: 'Home (iframe1)',
    message: '消息内容'
}, '*');

接收端(父页面):

// 父页面监听消息
window.addEventListener('message', function(event) {
    if (event.data.target === 'parent') {
        console.log('父页面处理消息:', event.data.message);
        // 在页面上显示日志
    }
});

父接收数据:

image.png

2.3 兄弟

对于兄弟页面,无法通过postMessage直接通讯,只能通过父页面进行中转,可以根据特定的类型,让父元素进行转发。具体不作介绍。

3. 实战案例:window.open打开方式通讯

通过window.open()打开新窗口后,父页可通过返回的窗口对象发送消息,子页通过window.opener获取父页窗口。

3.1 父->子

发送端(父页面):

const child = window.open(url)
child.contentWindow.postMessage({
    from: 'Parent (父页面)',
    message: '消息内容'
}, '*');

接收端(子页面):

window.addEventListener('message', function(event) {
});

3.2 子->父

发送端(子页面):

// 从子页面发送消息到父页面
window.opener.postMessage({
    message: '消息内容'
}, '*');

接收端(父页面):

// 父页面监听消息
window.addEventListener('message', function(event) {
});

3.3 兄弟

对于兄弟页面,无法通过句柄直接进行通讯,因为对于各自单独打开的页面,没法获取到窗口的句柄,也就没法进行消息的发送和监听。只能通过父页面进行中转,可以根据特定的类型,让父元素进行转发。具体不作介绍。

总结

最后总结一下:postMessage通过targetWindow.postMessage发送数据,其中targetWindow可以是iframe 的 contentWindow 属性或者执行window.open返回的窗口对象,通过监听message事件接收消息。

下一篇,我们将了解前端跨页面通讯的其他方案Broadcast Channel

如有错误,请指正O^O!

❌