普通视图

发现新文章,点击刷新页面。
昨天以前首页

前端跨页面通讯终极指南⑤:window.name 用法全解析

2025年12月7日 20:20

前言

在之前的文章里,介绍了 BroadcastChannel 的广播、postMessage 的灵活以及 MessageChannel 的精准。这些现代 API 为我们提供了标准的通信能力。

今天我们要介绍下——window.name。它不是一个为通信而生的 API,但是有其独特的“跨页面持久性”特性,成为解决跨域数据传输问题的方案之一。

1. window.name是什么?

window.name 是一个极其简单的属性,它的原始设计目的是用来设置或获取窗口(浏览器标签页)的名称。

// 设置窗口名称
window.name = 'My Awesome Window';

// 获取窗口名称
console.log(window.name); // 'My Awesome Window'

它有一个重要的特性:

只要是在同一个浏览器标签页中,即使页面发生了跳转(从一个域名到另一个域名),window.name 的值也不会被重置,它会一直保留。 更惊人的是,它的容量非常大,通常可以达到 2MB 左右!

2. window.name 的作用域

window.name 属性是属于每一个独立的窗口上下文(Window Context的。

  1. 父页面是一个窗口上下文,它有自己的 window.name
  2. iframe 子页面是另一个完全独立的窗口上下文,它也有自己window.name

它们是两个完全不同的变量,互不影响。当你在子页面中直接调用 window.name 时,你访问的是子页面自己name,而不是父页面的。

2.1 如何正确访问父页面的 window.name

虽然不能直接读取,但 iframe 提供了访问父窗口的路径:window.parent,可以通过iframe.contentWindow.name设置子页面的name。

关键点: iframe 的刷新只会重置它自己window.name,对父页面的 window.name 毫无影响。并且如果需要设置子页面的name,必须是同源。

只能读取同源子页面的name,否则会报错:

image.png

3. 实战案例

通过一个简单的例子来说明,具体看代码:

  1. 父页面 (parent.html)
<!DOCTYPE html>
<html lang="en">
<head>
    <title>父页面</title>
</head>
<body>
    <h1>这是父页面</h1>
    <p>父页面的 window.name: <strong id="parentName"></strong></p>

    <hr>
    <iframe src="child.html" id="myIframe" style="width: 100%; height: 300px; border: 1px solid black;"></iframe>

    <script>
        // 1. 设置父页面的 name
        window.name = "我是父页面的秘密数据";
        document.getElementById('parentName').textContent = window.name;

        console.log('父页面: 已设置 window.name =', window.name);
    </script>
</body>
</html>
  1. 子页面 (child.html)
<!DOCTYPE html>
<html lang="en">
<head>
    <title>子页面</title>
</head>
<body>
    <h2>这是 iframe 子页面</h2>
    <button onclick="location.reload()">刷新本页面</button>
    <hr>
    <p><strong>子页面自己的 window.name:</strong> <span id="childName"></span></p>
    <p><strong>通过 window.parent.name 访问父页面:</strong> <span id="parentNameFromChild"></span></p>

    <script>
        function updateDisplay() {
            // 读取子页面自己的 name
            document.getElementById('childName').textContent = window.name || '(空)';

            // 通过 window.parent.name 访问父页面的 name
            try {
                const parentName = window.parent.name;
                document.getElementById('parentNameFromChild').textContent = parentName;
            } catch (e) {
                document.getElementById('parentNameFromChild').textContent = '访问失败 (跨域?)';
            }
        }
        updateDisplay();

        // 为了演示,我们也可以设置一下子页面自己的 name
        window.name = "我是子页面自己的数据";
    </script>
</body>
</html>

子页面会显示:子页面自己的 window.name: 我是子页面自己的数据,子页面会显示:通过 window.parent.name 访问父页面: 我是父页面的秘密数据点击“刷新本页面”按钮后:子页面会重新加载,其自己window.name 会被重置为空字符串。但是,window.parent.name 的值依然是 我是父页面的秘密数据,完全不受影响。

4. 总结

最后总结一下:window.name作为浏览器的一个老古董方案,简单了解介绍下,如果需要通讯,还是推荐postMessage等通讯方式。

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

2025年12月7日 20:18

前言

上一篇介绍了Localstorage跨页面通讯的方式。在前面的文章中,介绍了多种跨页面通信方式,从适用于同源页面的 BroadcastChannel,到解决跨域的 postMessage。当多个通信进行混杂在一起,使用全局的message事件监听时,会通过各种类型判断消息来源进行处理。

那有没有一种方法,既能实现跨上下文通信,又能像打电话一样,建立起一条专属的、双向的、点对点的私密通道呢?

今天介绍一个方案——MessageChannel API。提供了一种更为优雅和私密的方式,建立起一条点对点的“专线电话”,让通信双方可以清晰地、无干扰地对话。

1. MessageChannel 是什么?

Channel Messaging API 的 MessageChannel 接口允许我们创建一个新的消息通道,并通过它的两个 MessagePort 属性发送数据。

核心是创建一个双向通讯的管道,这个管道包含两个相互关联的端口——port1port2。数据从 port1 发送,就只能由 port2 接收;反之,port2 发送的数据也只能被 port1 捕获,这种“点对点”的通讯模式,从根源上避免了数据被无关页面拦截的风险。

举个通俗的例子: BroadcastChannel 是“小区广播”,所有人都能听到;而 MessageChannel 就是“专线电话”,只有两个端口对应的设备能接通。

2. 如何使用

使用 MessageChannel 流程如下:

  1. 创建通道: 创建一个 MessageChannel 实例,包含两个端口属性:port1port2
const channel = new MessageChannel();

// channel.port1 和 channel.port2 都是 MessagePort 对象

image.png

  1. 监听消息: 在其中一个端口上设置 onmessage 事件处理器,用于接收来自另一个端口的消息。
channel.port1.onmessage = (event) => {
    console.log('收到消息:', event.data);
};
  1. 发送消息: 通过一个端口的 postMessage方法向另一个端口发送数据。
channel.port2.postMessage('Hello from port2!');
  1. 转移端口所有权MessageChannel 的威力在于可以将一个端口发送到另一个浏览上下文(例如 iframe)。这需要通过 window.postMessage 的第三个参数 transfer 来实现, 可以直接将端口下发到两个子iframe,直接进行通讯。
// 假设 iframe 是我们想要通信的目标
const iframe = document.querySelector('iframe').contentWindow;

// 将 port2 的所有权转移给 iframe
// 转移后,当前页面就不再拥有 port2,只有 iframe 能使用它

iframe.postMessage('init', '*', [channel.port2]);

使用postMessage发送端口会出现ports:

image.png

接收方(iframe)可以在其 message 事件中获取到这个端口,开始通信。

3. 实践场景

下面使用MessageChannel进行父子双向、兄弟通讯进行说明。

3.1 父子双向通讯

父页面引入一个 iframe,创建MessageChannel,初始化时将其中一个port1使用postMessage传递,后面直接通过port进行双向通讯。

步骤1:父页面(发送端口+通讯逻辑)

// 1. 创建 MessageChannel 实例,生成两个端口
const channel = new MessageChannel();
const { port1, port2 } = channel;

// 2. 获取 iframe 元素,监听加载完成事件
const iframe = document.getElementById('myIframe');
iframe.onload = () => {
  // 3. 向 iframe 传递 port2(关键:只有传递端口后才能通讯)
  iframe.contentWindow.postMessage('init', '*', [port2]);
};

// 4. 监听 port1 接收的消息(来自 iframe)
port1.onmessage = (e) => {
  console.log('父页面收到 iframe 消息:', e.data);
  // 收到消息后回复
  if (e.data === 'hello from iframe') {
    port1.postMessage('hi iframe, I am parent');
  }
};

// 5. 可选:监听错误事件
port1.onerror = (error) => {
  console.error('通讯错误:', error);
  port1.close(); // 出错后关闭端口
};

步骤2:iframe 页面(接收端口+响应逻辑)

// 1. 监听父页面发送的初始化消息
window.onmessage = (e) => {
  // 2. 验证消息类型,获取传递的 port2
  if (e.data === 'init' && e.ports.length) {
    const port2 = e.ports[0];

    // 3. 监听 port2 接收的消息(来自父页面)
    port2.onmessage = (msg) => {
      console.log('iframe 收到父页面消息:', msg.data);
    };

    // 4. 向父页面发送消息
    port2.postMessage('hello from iframe');
  }
};

需要注意的是:

  • 父页面通过 postMessage 传递 port2 时,必须将 port2 放在第三个参数(transferList)中,这是 “端口传递”的固定写法;
  • 端口一旦传递,父页面的 port2 就会失效,只能通过 iframe 中的 port2 通讯。

3.2 兄弟通讯

实现思路是: 父页面作为“总机”,负责创建 MessageChannel,并将两个端口分别分配给两个 iframe,让它们之间建立起直连专线。

步骤1:父页面代码

const channel = new MessageChannel();
const frame1 = document.getElementById('frame1').contentWindow;
const frame2 = document.getElementById('frame2').contentWindow;
// 等待两个 iframe 加载完成
let loadCount = 0;
const onLoad = () => {
  loadCount++;
  if (loadCount === 2) {
    // 将 port1 发送给 iframe1
    frame1.postMessage('init', '*', [channel.port1]);
    // 将 port2 发送给 iframe2
    frame2.postMessage('init', '*', [channel.port2]);
    console.log('父页面:专线已建立,端口已分发。');
  }
};
document.getElementById('frame1').onload = onLoad;
document.getElementById('frame2').onload = onLoad;

步骤2:子页面接收port


let port;
// 1. 接口端口
window.addEventListener('message', (event) => {
  // 确认是父页面发来的初始化消息,并接收端口
  if (event.data === 'init') {
    port = event.ports[0];
    console.log('iframe1:已接收 port1,准备发送消息。');
  }
});
// 2. 发送消息
document.getElementById('sendBtn').onclick = () => {
  if (port) {
    const message = `来自 iframe1 的问候,时间:${new Date().toLocaleTimeString()}`;
    port.postMessage(message);
    console.log('iframe1:消息已发送 ->', message);
  }
};
// 3. 监听 port 消息
port.onmessage = (e) => {
  console.log('页面B收到消息:', e.data);
};

实际效果:

image.pngimage.png

4. 注意事项

  1. 端口传递必须用 transferList

传递端口时,必须将 port 放在 postMessage 的第三个参数(transferList)中,而不是作为第一个参数(data)。错误写法会导致端口无法正常绑定,通讯失效。

  1. 通讯完成后及时关闭端口

不需要通讯时,调用 port.close() 关闭端口,避免内存泄漏。

5. 总结

最后总结一下:MessageChannel 通过创建一个专属的双向通道,解决了点对点通信的需求,唯一不足的是无论是父子还是兄弟通讯都是需要使用postMessage进行传递端口。

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

2025年12月2日 17:06

前言

上一篇介绍了BroadcastChannel跨页面通讯的方式。今天介绍一种我们非常熟悉的方式LocalStorage 。凭浏览器原生接口就能实现数据共享,用法简洁高效。需要注意,仅支持同源页面

下面我们介绍下LocalStorage 的跨页通讯的用法。

1. LocalStorage为什么能跨页通讯?

我们都知道LocalStorage——它是浏览器提供的同源本地存储方案,数据存储在客户端,生命周期为永久(除非手动删除或清除浏览器缓存)。

它能实现跨页通讯的关键,在于两个核心机制:

1.1 同源数据共享机制

LocalStorage 的数据严格遵循“同源策略”,即同一协议(http/https)、同一域名、同一端口下的所有页面,都能读取和修改同一个 LocalStorage 实例中的数据。

1.2 storage 事件触发机制

LocalStorage 实现“通讯”而非单纯“数据存储”的核心。当一个页面修改了 LocalStorage 中的数据时,浏览器会自动向同源下的所有其他页面触发一个 storage 事件,该事件会携带修改前、修改后的数据及键名等信息。其他页面通过监听这个事件,就能实时感知数据变化,从而完成跨页通讯。

注意:当前页面修改 LocalStorage 时,自身不会触发 storage 事件,只有同源的其他页面才会收到通知!

1.3 LocalStorage通讯流程

LocalStorage 跨页通讯的核心流程是:

  1. 页面A修改 LocalStorage 数据 → 浏览器向同源其他页面发送 storage 事件
  2. 页面B/C/D 监听事件并获取数据变化。

2. 实践案例

通过上面的说明,作为数据发送方,通过修改 LocalStorage 存储数据;作为接收方,监听 storage 事件获取父页面传递的信息。我们实践一下:

2.1 步骤1:数据发送

通过 localStorage.setItem() 存储数据,触发 storage 事件。为避免数据覆盖,建议给键名添加场景标识(如 parent-to-iframe-msg)。

// 发送数据
function sendToIframe(data) {
  // 1. 存储数据到 LocalStorage,键名需唯一标识通讯场景
  localStorage.setItem('parent-to-iframe-msg', JSON.stringify({
    timestamp: Date.now(), // 防止数据缓存导致事件不触发
    content: data
  }));
  
  // 2. 可选:若需重复发送相同数据,可先删除再添加(storage 事件仅在值变化时触发)
  // localStorage.removeItem('parent-to-iframe-msg');
}

// 调用方法发送数据(示例:传递用户信息)
sendToIframe({
  username: '前端小助手',
  role: 'admin'
});

2.2 步骤2:数据接收

接收方页面通过监听 window.addEventListener('storage', callback) 捕获数据变化,解析后获取页面传递的内容。

// 监听父页面发送的数据
window.addEventListener('storage', (e) => {
  // 1. 仅处理目标键名的数据变化,避免无关事件干扰
  if (e.key !== 'parent-to-iframe-msg') return;
  
  // 2. 解析数据(注意:初始状态下 e.newValue 可能为 null)
  if (!e.newValue) return;
  
  const { timestamp, content } = JSON.parse(e.newValue);
  console.log('iframe 收到父页面数据:', content);
  
  // 3. 业务处理:如渲染用户信息
  document.getElementById('user-info').innerText = `用户名:${content.username},角色:${content.role}`;
});

// 可选:页面销毁时移除监听,避免内存泄漏
window.addEventListener('beforeunload', () => {
  window.removeEventListener('storage', handleStorage);
});

接收数据如下:

image.png

3. LocalStorage通讯注意事项

LocalStorage 用法简单,但在跨页通讯中若忽略细节,很容易出现“数据发了但收不到”的问题。以下这些坑必须提前规避:

3.1 同源策略限制:跨域页面无法通讯

LocalStorage 严格遵循同源策略,不同域名、协议或端口的页面无法共享数据,也无法触发 storage 事件。若需跨域通讯,需结合 postMessage 或服务器中转,LocalStorage 无法单独实现。

3.2 数据格式限制:仅支持字符串类型

LocalStorage 只能存储字符串,若要传递对象、数组等复杂数据,必须用 JSON.stringify() 序列化,接收时用 JSON.parse() 反序列化。注意:undefined、function 等类型无法被正常序列化,需提前处理。

3.3 storage 事件触发条件:仅值变化时触发

只有当 LocalStorage 中数据的“值”发生变化时,才会触发 storage 事件。若两次存储相同的值,事件不会触发。解决办法:在数据中添加 timestamp 时间戳或随机数,确保每次存储的值不同。

3.4 存储容量限制:避免数据过大

LocalStorage 单个域名的存储容量约为 5MB,若存储数据过大,会导致存储失败。跨页通讯应仅传递必要的核心数据(如 ID、状态),避免传递大量文本或二进制数据。

4. 总结

最后总结一下:LocalStorage只需要操作 setItemgetItem 和 监听 storage 事件就能实现同源通讯。如果是非同源,那就只能用其他方式。

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

2025年11月30日 17:50

前言

上一篇介绍了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兄弟页面无需转发,几行代码就能轻松实现通讯。

❌
❌