跨标签通信的几种方式

以前面试被问到过,就了解了一下。还有其他方式,但是实际开发中,使用第一个就可以了

目录

[1. 使用BroadcastChannel](#1. 使用BroadcastChannel)

[2. 使用SharedWorker](#2. 使用SharedWorker)

[3. 使用webSocket](#3. 使用webSocket)


1. 使用BroadcastChannel

它允许同源(协议、域名、端口都相同)的不同浏览器窗口、标签页、frame 或者 iframe 下的不同文档之间相互通信。

新建一个文件夹后,新建下面两个文件

a页面

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>

<body>
	<p id="count">0</p>
	<button>count++</button>
	<script>
		// 在所有标签页中创建一个BroadcastChannel实例
		const channel = new BroadcastChannel('my-channel')

		const count = document.getElementById('count')
		const btn = document.querySelector('button')
		btn.addEventListener('click', () => {
			count.innerHTML = parseInt(count.innerHTML) + 1
			// 发送消息
			channel.postMessage({ type: 'my-data', payload: parseInt(count.innerHTML) });
		})
	</script>
</body>

</html>

b页面

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>

<body>
	<p></p>
	<script>
		// 在所有标签页中创建一个BroadcastChannel实例
		const channel = new BroadcastChannel('my-channel');

		var p = document.querySelector('p');

		// 监听消息
		channel.addEventListener('message', (event) => {
			if (event.data.type === 'my-data') {
				p.innerText = event.data.payload;
			}
		})
	</script>
</body>

</html>

每次实例化**BroadcastChannel**构造函数,都会创建一个链接到命名频道的对象。

而命名频道则是给定源(协议、域名、端口)后产生的一个独有频道。只要源(协议、域名、端口)相同的,通过**BroadcastChannel** 实例化后的对象,都可以进行通信

  • postMessage方法向所有监听了相同频道的 BroadcastChannel 对象发送一条消息,消息内容可以是任意类型的数据。
  • message方法当频道收到一条消息时触发。

2. 使用**SharedWorker**

SharedWorker 接口代表一种特定类型的 worker,可以从几个浏览上下文中访问,例如几个窗口、iframe 或其他 worker

  • 如果要使 SharedWorker 连接到多个不同的页面,这些页面必须是同源的(相同的协议、host 以及端口)
  • Worker则是在后台中创建一个Worker线程,不会影响主线程,Worker线程中的代码执行完成后,会自动塞入到任务队列,等待执行。可以把一些特别耗时,计算量比较大的放到Worker线程中单独执行,依次达到性能优化的作用
  • SharedWorker 顾名思义,会共享线程中的数据

创建的Vue3项目

在public文件夹下新建shared-worker文件夹,然后新建SharedWorker.js

javascript 复制代码
// 计时器
let counter = 0
// 存储所有连接端口的数组
let ports = []

// 监听连接
self.addEventListener('connect', e => {
  const port = e.ports[0]

  // 把端口对象存起来
  ports.push(port)

  // 监听消息
  port.onmessage = res => {
    // console.log('共享线程接收到信息:', res.data)
    switch (res.data) {
      case 'counter++':
        counter++
        break
    }
    // console.log('counter:', counter)
    // 发送消息
    // port.postMessage(counter)

    // 向所有端口广播,这样就都能接收到发送的消息,才会触发message事件
    ports.forEach(p => {
      p.postMessage(counter)
    })
  }
})

HomeView.vue

javascript 复制代码
<template>
  <div>
    <h3>{{ data }}</h3>
    <button @click="worker.port.postMessage('counter++')">
      点击向共享线程发送消息
    </button>
  </div>
</template>

<script setup lang="ts">
import { onUnmounted, ref } from 'vue'

const data = ref(0)

// 兼容性判断
if (!window.SharedWorker) {
  throw new Error('当前浏览器不支持SharedWorker')
}

// 创建共享线程
const worker = new SharedWorker('/shared-worker/SharedWorker.js')

// 启动线程端口
worker.port.start()

// 消息处理
const messageHandle = e => {
  console.log('SharedWorker共享线程counter值:', e.data)
  data.value = e.data
}

// 线程监听消息
worker.port.addEventListener('message', messageHandle)

// 记得销毁worker线程
onUnmounted(() => {
  worker.port.removeEventListener('message', messageHandle)
  worker.port.close()
})
</script>

AboutView.vue

javascript 复制代码
<template>
  <div>
    <h3>{{ data }}</h3>
    <button @click="worker.port.postMessage('counter++')">
      点击向共享线程发送消息
    </button>
  </div>
</template>

<script setup lang="ts">
import { onUnmounted, ref } from 'vue'

const data = ref(0)

// 兼容性判断
if (!window.SharedWorker) {
  throw new Error('当前浏览器不支持SharedWorker')
}

// 创建共享线程
const worker = new SharedWorker('/shared-worker/SharedWorker.js')

// 启动线程端口
worker.port.start()

// 消息处理
const messageHandle = e => {
  console.log('SharedWorker共享线程counter值:', e.data)
  data.value = e.data
}

// 线程监听消息
worker.port.addEventListener('message', messageHandle)

// 记得销毁
onUnmounted(() => {
  worker.port.removeEventListener('message', messageHandle)
  worker.port.close()
})
</script>

3. 使用webSocket

前端:

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>WebSocket 测试</h1>
    <p>WebSocket 是一种网络通信协议,提供全双工、双向的通信。</p>
    <input type="text" />
    <button>发送消息</button>

    <script>
      const ws = new WebSocket("ws://127.0.0.1:3001");
      const input = document.querySelector("input");
      const btn = document.querySelector("button");

      ws.onopen = function () {
        console.log("连接成功");
      };

      ws.onmessage = function (e) {
        console.log("收到服务端信息:" + e.data);
        input.value = e.data;
      };

      ws.onclose = function () {
        console.log("连接已关闭");
      };

      ws.onerror = function () {
        console.log("连接出错");
      };

      // 向服务端发送消息
      btn.addEventListener("click", function () {
        console.log("发送消息:" + input.value);
        ws.send(input.value);
      });
    </script>
  </body>
</html>

后端:使用express创建的node项目

安装node后,全局安装express:npm i -g express

然后创建node项目:express 项目名

创建项目完成后,在项目根目录下安装ws包和nodemon:npm i nodemon ws

修改package.json中的scripts节点:把node改为nodemon运行,这样能实现热更新

修改app.js

javascript 复制代码
var createError = require("http-errors");
var express = require("express");
var path = require("path");
var cookieParser = require("cookie-parser");
var logger = require("morgan");

var indexRouter = require("./routes/index");
var usersRouter = require("./routes/users");

var app = express();

// 实现webSocket start ----
const WebSocket = require("ws");
// 创建WebSocket服务器,监听端口3001,express服务器的端口是3000,端口要不一样
const server = new WebSocket.Server({ port: 3001 });
server.on("connection", (ws) => {
  console.log("New client connected");

  ws.on("message", (message) => {
    console.log(`Received: ${message}`);

    // 广播收到的消息给所有客户端
    server.clients.forEach((client) => {
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(message.toString());
      }
    });
  });

  ws.on("close", () => {
    console.log("Client disconnected");
  });
});
// 实现webSocket end ----

// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "jade");

app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));

app.use("/", indexRouter);
app.use("/users", usersRouter);

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get("env") === "development" ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render("error");
});

module.exports = app;

然后运行后端项目即可:npm start

或者使用socket.io插件

客户端:Vue3项目

javascript 复制代码
<template>
  <div>
    <el-input v-model="data" @change="handleInput"></el-input>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { io } from 'socket.io-client'

const data = ref('')

const socket = io('http://localhost:3001', {
  autoConnect: false, // 自动连接
  // 传递参数
  query: {
    name: localStorage.getItem('user'),
  },
  // 设置请求头
  extraHeaders: {
    token: 'Bearer authorization_token_here',
  },
  reconnection: true, // 自动重连
  reconnectionAttempts: 10, // 重连次数
})

// 监听连接状态
socket.on('connect', function () {
  console.log('客户端链接成功')
})

// 监听断开连接
socket.on('disconnect', function () {
  console.log('客户端断开连接')
})

// 连接
socket.connect()

// 监听输入框
const handleInput = () => {
  socket.emit('chatMessage', data.value)
}
// 实时接收后台返回的数据
socket.on('chat message', function (msg) {
  console.log('接收消息', msg)
  data.value = msg.data
})
</script>

服务端:app.js

javascript 复制代码
var createError = require("http-errors");
var express = require("express");
var path = require("path");
var cookieParser = require("cookie-parser");
var logger = require("morgan");

var indexRouter = require("./routes/index");
var usersRouter = require("./routes/users");
// const cors = require("cors");

var app = express();
// app.use(cors());

// socket.io 创建web服务,不同端口
const http = require("http");
const server = http.createServer();
const { Server } = require("socket.io");
const io = new Server(server, {
  cors: {
    origin: "*", // 配置客户端可跨域地址
  },
});
io.on("connection", (socket) => {
  let { name } = socket.handshake.query;
  console.log("客户端连接成功,参数:: ", name);
  let { token } = socket.handshake.headers;
  console.log("客户端连接成功,token: ", token);
  // 监听客户端请求
  socket.on("chatMessage", (msg) => {
    // 广播给所有客户端
    io.emit("chat message", {
      status: 200,
      data: msg,
    });
  });

  // 监听断开事件
  socket.on("disconnect", (reason) => {
    console.log("断开链接原因:", reason);
  });
});
server.listen(3001, () => {
  console.log("3001服务启动成功!");
});
// socket.io ------------

// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "jade");

app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));

app.use("/", indexRouter);
app.use("/users", usersRouter);

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get("env") === "development" ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render("error");
});

module.exports = app;
相关推荐
腾讯TNTWeb前端团队6 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy10 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom11 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom11 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom11 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom11 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试