【技术分享】前端跨窗口/标签页面通信:掌握以下几种方法,提升用户体验(附带常用场景以及典例)

第一章 前言

在前端后台管理开发的过程中,我们常常会遇到这样的需求:用户在浏览器中打开了同一个网站的多个标签页,我们需要在这些标签页之间进行通信。比如,当用户在一个标签页中修改了某些数据,我们希望其他标签页能够及时更新。这种跨标签页通信的需求在实际开发中非常常见,但是同时也是经常忽略的一个点,通常说刷一下之后就好了,不用那么麻烦。但是小编为了解决这一问题,来与大家分享一下如何实现前端跨标签页通信。

第二章 需要跨标签通信的原因

在实际的业务场景中,跨标签页通信的需求主要体现在以下几个方面:

  1. 数据同步:当用户在多个标签页中操作同一个数据时,需要保持数据的一致性。例如,在一个电商网站中,用户可能在多个标签页中查看不同的商品,当用户在一个标签页中将商品加入购物车时,其他标签页的购物车数量也需要及时更新。

  2. 状态共享:有些应用需要在多个标签页之间共享状态。比如,一个在线文档编辑器,用户可能在多个标签页中打开同一个文档,需要保持文档的编辑状态一致。

  3. 性能优化:通过跨标签页通信,我们可以避免在每个标签页中重复加载相同的数据,从而提高应用的性能。

第三章 实现跨标签通信的方法以及优缺点

3.1 方法一:BroadcastChannel

  • 原理:BroadcastChannel 是浏览器原生支持的跨标签页通讯 API,允许在同一源(origin)下的不同标签页之间进行消息传递。
  • 发送与接收端代码如下:

发送端:(小编这里做的是跨页面列表初始化的功能)

javascript 复制代码
// 创建一个 BroadcastChannel 实例
const channel = new BroadcastChannel('my-channel')
// 发送消息
channel.postMessage({
  type: 'init',
  data: {
    page: 1,
    limit: 10
  }
})

接收端

javascript 复制代码
// 创建一个 BroadcastChannel 实例
const channel = new BroadcastChannel('my-channel')

// 监听消息
channel.onmessage = (event) => {
  console.log('Received message:', event.data)
  if (event.data.type === 'init') {
    const { page, limit } = event.data.data
    currentPage.value = page
    pageSize.value = limit
    // 初始化列表
    getList()
  }
}
  • 优点
  1. API 设计简单,易于理解和使用
  2. 消息是实时传递的
  3. 即使数据没有改变也会检测到
  • 缺点
  1. 只能在相同源(协议 + 域名 + 端口)的标签页之间通信

3.2 方法二:localStorage

  • 原理:使用localStorage能实现是因为我们开出的新标签页在都是同一个协议、域名、端口号下 ,它们的本地存储内容是共享的!(也就是同源下的不同标签页之间可以共享数据)
  • 使用localStorage(看技术栈,如果是vue,也可以用vuex/pinia等,原理是类似的),发送与接收端代码如下:

发送端

javascript 复制代码
// 先清除原来数据
localStorage.removeItem('PAGE_INIT')
// 重新赋值(这样做接受端一定能检测到变化)
localStorage.setItem('PAGE_INIT', JSON.stringify({ page: 1, limit: 10 }))

// 注:其实不用跟小编一样,这么处理只要能让本地存储的数据变化就能实现需求

接收端

javascript 复制代码
// 监听存储信息
window.addEventListener('storage', (e) => {
  if (e.key === 'PAGE_INIT') {
    console.log('e.newValue', e)
    const { page, limit } = JSON.parse(e.newValue)
    console.log('数据',page, limt )
    currentPage.value = page
    pageSize.value = limit
    getList()
  }
})
  • 优点
  1. localStorage浏览器原生自带的,兼容性高,几乎所有浏览器都兼容
  2. 使用简单,发送消息的窗口不会触发监听事件
  3. 数据持久化
  • 缺点
  1. 新标签必须遵守同源协议接受端才能接收到(同源策略)
  2. 如果监听的值未发生改变也不会触发事件
  3. 当前标签页不会触发

3.3 方法三:SharedWorker

  • 原理:ShareWorker是一种特殊的 Web Worker,允许多个标签页共享同一个 Worker 实例,从而实现跨标签页通信
  • 新建worker文件下添加shared-worker.js文件
javascript 复制代码
// shared-worker.js

// 每个页面都会创建一个port
const ports = [];

onconnect = (event) => {
    console.log('shared-worker onconnect', event);
    // 获取当前页的port
    const port = event.ports[0];

    // 将当前页面的port加入到数组
    ports.push(port);

    port.onmessage = (event) => {
        // 广播消息到所有连接的标签页
        ports.forEach((p) => {
            if (p !== port) {
                p.postMessage(event.data);
            }
        });
    };
};
  • 发送与接收端代码如下:

发送端:

javascript 复制代码
import workerjs from '@/worker/shared-worker.js?url'

// 创建实例
const worker = new SharedWorker(workerjs)

// 发送消息到shared-worker
worker.port.postMessage({
  type: 'init',
  data: {
    page: 1,
    size: 10
  }
})

接收端:

javascript 复制代码
import workerjs from '@/worker/shared-worker.js?url'

// 创建实例
const worker = new SharedWorker(workerjs)

// 接收消息
worker.port.onmessage = (event) => {
  console.log('shared-worker onmessage', event.data)
  if (event.data.type === 'init') {
    // 初始化
    const {
      data: { page, size }
    } = event.data
    currentPage.value = page
    pageSize.value = size
    getList()
  }
}
  • 优点
  1. 多个标签页共享同一个 Worker 实例
  2. 适合需要复杂逻辑处理的场景
  • 缺点
  1. 部分浏览器或者低版本浏览器可能不支持
  • 官方文档

SharedWorker - Web API | MDN

3.4 方法四:Window.postMessage

  • 原理:通过 Window.postMessage 可以在不同窗口或标签页之间传递消息。
  • 可以看小编下面这篇文章:

iframe: 通过postMessage实现父子页面通信

(注意:小编在跨标签页通信时,不推荐该方法,可看下面的缺点)

  • 优点
  1. 可以在不同源的窗口或标签页之间通信
  2. 可以指定目标窗口的origin,从而确保消息来源可信(安全性)
  • 缺点
  1. 需要持有目标窗口的引用,即需要开新的标签页,原有标签页没法监听到(理解:目前已有标签页1、标签页2,需要在标签页2做某些处理,标签页1能监听到消息没法实现,只能在标签页2开新的窗口标签页3)

3.5 方法五:Service Worker

  • 原理:Service Worker 可以拦截网络请求并缓存资源,同时也可以用于跨标签页通信。
  • 发送端与接收端代码如下:
javascript 复制代码
// 注册 Service Worker
navigator.serviceWorker.register('service-worker.js');

// 发送消息
navigator.serviceWorker.controller.postMessage({
  type: 'init',
  data: {
    page: 1,
    limit: 10
  }
});

// 接收消息
navigator.serviceWorker.onmessage = (event) => {
  console.log('Received message:', event.data);
}
  • 优点:
  1. Service Worker 可以用于离线缓存
  2. 适合需要复杂逻辑处理的场景
  • 缺点:
  1. 部分浏览器或者低版本浏览器可能不支持

3.6 方法六:IndexedDB

  • IndexedDB 是一种浏览器端数据库,可以存储大量结构化数据。通过监听 IndexedDB 的变化,可以实现跨标签页通信
  • 该方案小编也没有具体实操过,代码如下:

发送端将信息写入IndexedDB:

javascript 复制代码
const data = {
  type: 'init',
  data: {
    page: 1,
    limit: 10
  }
};
const dbName = "simpleDB";
const storeName = "SharedData";
const key = "sharedKey";

const request = indexedDB.open(dbName, 1);

request.onupgradeneeded = e => {
  const db = e.target.result;
  if (!db.objectStoreNames.contains(storeName)) {
    db.createObjectStore(storeName);
  }
};

request.onsuccess = e => {
  const db = e.target.result;
  console.log("db", db);
  const tx = db.transaction(storeName, "readwrite");
  const store = tx.objectStore(storeName);

  // 保存数据
  store.put(data, key);

  tx.oncomplete = () => {
     // 关闭连接
    db.close();
  };
};

接收端监听IndexedDB信息变化

javascript 复制代码
getDBData() {
    const dbName = "simpleDB";
    const storeName = "SharedData";
    const key = "sharedKey";

    const request = indexedDB.open(dbName, 1);

    request.onupgradeneeded = e => {
      const db = e.target.result;
      if (!db.objectStoreNames.contains(storeName)) {
        db.createObjectStore(storeName);
      }
    };

    request.onsuccess = e => {
      const db = e.target.result;
      const tx = db.transaction(storeName, "readonly");
      const store = tx.objectStore(storeName);
      const getRequest = store.get(key);

      getRequest.onsuccess = () => {
        const data = getRequest.result;
        db.close();
      };
    }
}
// 监听数据变化--这里使用setInterval
setInterval(() => {
    getDB();
}, 2000);
// 版本号变化监听-onversionchange
db.onversionchange = () => {
    console.log('数据库已更新,重新加载数据...');
}
  • 优点:
  1. 适合存储大量数据
  2. 所有操作都是异步的
  3. 几乎所有现代浏览器都支持
  • 缺点:
  1. 需要大家具体实操感受
  • 注意问题
  1. 无痕模式下无法使用indexedDB
  2. 取决于浏览器用户的主动行为,当用户拒绝访问时,不被允许

3.7 方法七:WebSocket

  • 这里不做过多介绍,该方法需要前后端配合,有点"大材小用"
  • 有兴趣可以看小编下面这篇文章(基础篇):

websocket: 了解并利用nodejs实现webSocket前后端通信

第四章 总结

|--------------------|------------------|--------------------------|---------------|
| 方法 | 特点 | 适用场景 | 兼容性 |
| BroadcastChannel | 简单易用,实时性强,同源限制 | 同源标签页之间的实时通信(基本上简单需求都能用) | 低版本ie除外 |
| localStorage | 兼容性好,数据持久化,事件触发 | 简单的状态同步 | 基本上支持 |
| SharedWorker | 共享实例,适合复杂场景 | 需要复杂逻辑处理的场景 | IE 和部分移动浏览器除外 |
| Window.postMessage | 跨域支持,安全性高,需要窗口跳转 | 跨域窗口或标签页之间的通信 | 基本上支持 |
| Service Worker | 离线支持,适合复杂场景 | 需要离线支持的场景 | ie除外 |
| IndexedDB | 大数据支持,异步操作 | 需要存储大量数据的场景 | 基本上支持 |
| WebSocket | 前后端,实时 | 实时通信 | 基本上支持 |

第五章 典例

【案例分享】前端跨窗口/标签页通信经典案例!

相关推荐
一个儒雅随和的男子35 分钟前
Web开发身份认证技术解析
前端·github
小二·36 分钟前
DevUI 和 MateChat:2025 年,我们是怎么把前端开发变轻松的
开发语言·javascript·vue.js
G***E31639 分钟前
前端自动化测试工具:8个主流方案对比
前端·测试工具·自动化
●VON1 小时前
基于 Electron 模拟鸿蒙设备硬件信息查询的可行性探索
javascript·学习·electron·openharmony
一 乐1 小时前
游戏账号交易|基于Springboot+vue的游戏账号交易系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·游戏
西洼工作室1 小时前
前端自制设备模拟器模拟不同终端展示效果
前端·css3·js·响应式开发
ByteCraze1 小时前
面向Nodejs开发人员MCP快速入门
前端·node.js·agent·mcp
chéng ௹1 小时前
前端转编码(encodeURIComponent)以及解码(decodeURIComponent)
开发语言·前端·javascript
温轻舟1 小时前
禁毒路上,任重道远 | HTML页面
开发语言·前端·javascript·css·html·温轻舟