A.vue
<template>
<div>
<div>{{ count }}</div>
<button @click="handleNewDialog">B弹窗</button>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useRouter } from 'vue-router' //引入useRouter
const count = ref(1);
const router = useRouter();
const {href} = router.resolve({ //使用resolve
name:'B', //这里是跳转页面的name
path: '/B',
query: {
count: count.value,
}
})
window.name = 'A'
function handleNewDialog() {
window.open(href, '_blank', centerStyle(400, 400)+',toolbar=no,menubar=no,resizeable=no,location=no,status=no,scrollbars=yes')
}
// 子方法
var centerStyle = function (height, width) {
var iTop = (window.screen.height - 30 - height) / 2; //获得窗口的垂直位置;
// var iLeft = (window.screen.width - 10 - width) / 2; //获得窗口的水平位置; 不生效
let iLeft = (window.screenX || window.screenLeft || 0) + (window.screen.width - width) / 2;
return 'height=' + height + ',width=' + width + ',top=' + iTop + ',left=' + iLeft
};
window.addEventListener('hashchange', function () {// 监听 hash
let hash = window.location.hash;
let index = hash.indexOf('?');
let searchData = hash.substring(index + 1);
let arr = searchData.split('=');
count.value = arr[1];
}, false);
</script>
<style>
</style>
B.vue
<template>
<div>
<div>{{ newCount }}</div>
<button @click="handelAdd">增加count</button>
<button type="button" @click="sendA()">发送A页面消息,关闭B弹窗,newCount变更后并显示在A页面上</button>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useRoute, useRouter } from 'vue-router'
window.name = 'B'
let newCount = ref(0);
const route = useRoute()
newCount.value = route.query.count
function handelAdd() {
newCount.value ++
}
// 窗口崩溃
window.onbeforeunload = function (e) {
sendA()
return '确定离开此页吗?';
}
function sendA() {
let href = window.location.origin + '/#/?count=' + newCount.value
window.open(href, 'A')
}
</script>
<style>
</style>
// A页面存储count, 监听获取数据,更新页面
localStorage.setItem('count', count.value);
// A页面 监听获取数据 storage事件只有在值发生变化时才会触发。
window.addEventListener('storage', function (e) {
})
// B页面获取count B修改之后更新localStorage存储的count
let testB = localStorage.getItem('count');
注:localStorage仅允许方位同源,存储的数据将保存在浏览器会话中,如果A打开的B页面和A是不同源的,则无法方位同一Storage
postMessage 是 html5 引入的API,postMessage()方法允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档、多窗口、跨域消息传递,多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案。
A.vue
<template>
<div>
<div>{{ count }}</div>
<button @click="handleNewDialog">new弹窗</button>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useRouter } from 'vue-router' //引入useRouter
const count = ref(1);
const router = useRouter();
const {href} = router.resolve({ //使用resolve
name:'B', //这里是跳转页面的name
path: '/B',
query: {
count: count.value,
}
})
window.name = 'A'
function handleNewDialog() {
window.open(href, '_blank', centerStyle(400, 400)+',toolbar=no,menubar=no,resizeable=no,location=no,status=no,scrollbars=yes')
}
// 子方法
var centerStyle = function (height, width) {
var iTop = (window.screen.height - 30 - height) / 2; //获得窗口的垂直位置;
// var iLeft = (window.screen.width - 10 - width) / 2; //获得窗口的水平位置; 不生效
let iLeft = (window.screenX || window.screenLeft || 0) + (window.screen.width - width) / 2;
return 'height=' + height + ',width=' + width + ',top=' + iTop + ',left=' + iLeft
};
window.addEventListener("message", receiveCount, false);
</script>
<style>
</style>
B.vue
<template>
<div>
<div>{{ newCount }}</div>
<button @click="handelAdd">增加count</button>
<button type="button" @click="sendA()">发送A页面消息</button>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useRoute, useRouter } from 'vue-router'
window.name = 'B'
let newCount = ref(0);
const route = useRoute()
newCount.value = route.query.count
function handelAdd() {
newCount.value ++
}
function sendA() {
// window.opener----是window.open打开的子页面对象调用父页面对象
// 通常在使用window.opener的时候要去判断父窗口的状态,如果父窗口被关闭或者更新,就会出错,解决办法是加上如下的验证if(window.opener && !window.opener.closed)
let targetWindow = window.opener
targetWindow.postMessage(newCount.value, window.location.href);
}
</script>
<style>
</style>

API介绍
otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow:窗口的引用,例如:比如执行window.open返回的窗口对象 iframe的contentWindow属性或者是命名过的或数值索引的window.frames.
message:要发送给其他窗口的数据
targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,指定后只有对应origin下的窗口才可以接收到消息,设置为通配符"*“表示可以发送到任何窗口,但通常处于安全性考虑不建议这么做.如果想要发送到与当前窗口同源的窗口,可设置为”/"
transfer (可选属性):是一串和message同时传递的Transferable对象,这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权.
window.addEventListener("message", receiveMessage, false) ;
function receiveMessage(event) {
var origin= event.data;
console.log(event);
}

SharedWorker 接口代表一种特定类型的 worker,可以从几个浏览上下文中访问,例如几个窗口、iframe 或其他 worker。它们实现一个不同于普通 worker 的接口,具有不同的全局作用域,SharedWorkerGlobalScope 。
page.vue
<template>
<div>{{ count }}</div>
<div @click="sendMessage">点击1</div>
</template>
<script setup>
import sharedWorkerHook from './sharedWorkerHook.js'
import { ref, onMounted } from "vue";
let count = ref(1);
onMounted(() => {
sharedWorkerHook.port.start()
// 接收SharedWorker返回的结果
sharedWorkerHook.port.onmessage = event => {
count.value = event.data;
console.log(event.data, '11111111111111')
}
})
function sendMessage() {
count.value ++
sharedWorkerHook.port.postMessage({ value: count.value })
}
</script>
page2.vue
<template>
{{ count }}
<div @click="sendMessage">点击2</div>
</template>
<script setup>
import sharedWorkerHook from './sharedWorkerHook'
import { ref, onMounted } from "vue";
let count = ref(100);
onMounted(() => {
sharedWorkerHook.port.start()
// 接收SharedWorker返回的结果
sharedWorkerHook.port.onmessage = event => {
count.value = event.data
console.log(event.data, '22222222222')
}
})
function sendMessage() {
count.value ++
sharedWorkerHook.port.postMessage({ value: count.value })
}
</script>
worker.js
/**
* @description 所有连接这个worker的集合
*/
const portsList = []
/**
* @description 连接成功回调
*/
self.onconnect = (event) => {
// 当前触发连接的端口
const port = event.ports[0]
// 添加进去
portsList.push(port)
// 接收到消息的回调
port.onmessage = (event) => {
// 获取传递的消息
const { type, message, value } = event.data
// 计算
let result = 0
result = value
portsList.forEach((port) => port.postMessage(`${result}`))
}
}
sharedWorkerHook.js
const sharedWorker = new SharedWorker(new URL('./worker.js', import.meta.url), 'test')
export default sharedWorker
文件目录结构

ShareWorker的Web API 接口
Service Worker 是一个可以长期运行在后台的 Worker,能够实现与页面的双向通信。多页面共享间的 Service Worker 可以共享,将 Service Worker 作为消息的处理中心(中央站)即可实现广播效果。
// 注册 Service Worker
navigator.serviceWorker.register('./sw.js').then(function () {
console.log('Service Worker 注册成功');
})
// 其中./sw.js是对应的Service Worker脚本。Service Worker本身并不具备“广播通信”的功能, 需要我们将其改造成消息中转站:
self.addEventListener('message', function (e) {
console.log(e.data);
e.waitUntil(
self.clients.matchAll().then(function (clients) {
if (!clients || clients.length === 0) {
return;
}
clients.forEach(function (client) {
client.postMessage(e.data);
});
})
);
});
// A 在需要获取的页面监听Service Worker发送来的消息:
navigator.serviceWorker.addEventListener('message', function (e) {
console.log(e.data)
});
// B 发送消息,可以调用Service Worker的postMessage方法:
navigator.serviceWorker.controller.postMessage('Hello A');
Service Worker - 《阮一峰 Web API 教程》
B 页面正常关闭,如何通知 A 页面
页面正常关闭时,会先执行 window.onbeforeunload ,然后执行 window.onunload ,我们可以在这两个方法里向 A 页面通信
B 页面意外崩溃,又该如何通知 A 页面
页面正常关闭,我们有相关的 API,崩溃就不一样了,页面看不见了,JS 都不运行了,那还有什么办法可以获取B页面的崩溃?
window.addEventListener('load', function () {
sessionStorage.setItem('good_exit', 'pending');
setInterval(function () {
sessionStorage.setItem('time_before_crash', new Date().toString());
}, 1000);
});
window.addEventListener('beforeunload', function () {
sessionStorage.setItem('good_exit', 'true');
});
if(sessionStorage.getItem('good_exit') &&
sessionStorage.getItem('good_exit') !== 'true') {
/*
insert crash logging code here
*/
alert('Hey, welcome back from your crash, looks like you crashed on: ' + sessionStorage.getItem('time_before_crash'));
}
这个方案巧妙的利用了页面崩溃无法触发 beforeunload 事件来实现的。
需要注意的是,使用sessionStorage存储状态可能会因为用户强制关闭网页或者重新打开浏览器而丢失,而将状态存储在localStorage或Cookie中可能会导致每有一次网页打开,就会有一个crash上报。因此,需要根据实际情况选择合适的存储方式。!
// B
if (navigator.serviceWorker.controller !== null) {
let HEARTBEAT_INTERVAL = 5 * 1000 // 每五秒发一次心跳
let sessionId = uuid() // B页面会话的唯一 id
let heartbeat = function () {
navigator.serviceWorker.controller.postMessage({
type: 'heartbeat',
id: sessionId,
data: {} // 附加信息,如果页面 crash,上报的附加数据
})
}
window.addEventListener("beforeunload", function() {
navigator.serviceWorker.controller.postMessage({
type: 'unload',
id: sessionId
})
})
setInterval(heartbeat, HEARTBEAT_INTERVAL);
heartbeat();
}
// 每 10s 检查一次,超过15s没有心跳则认为已经 crash
const CHECK_CRASH_INTERVAL = 10 * 1000
const CRASH_THRESHOLD = 15 * 1000
const pages = {}
let timer
function checkCrash() {
const now = Date.now()
for (var id in pages) {
let page = pages[id]
if ((now - page.t) > CRASH_THRESHOLD) {
// 上报 crash
delete pages[id]
}
}
if (Object.keys(pages).length == 0) {
clearInterval(timer)
timer = null
}
}
worker.addEventListener('message', (e) => {
const data = e.data;
if (data.type === 'heartbeat') {
pages[data.id] = {
t: Date.now()
}
if (!timer) {
timer = setInterval(function () {
checkCrash()
}, CHECK_CRASH_INTERVAL)
}
} else if (data.type === 'unload') {
delete pages[data.id]
}
})
对于同源页面
- 广播模式:Broadcast Channe / Service Worker / LocalStorage + StorageEvent
- 共享存储模式:Shared Worker / IndexedDB / cookie
- 口口相传模式:url传参(window.open + window.opener)
- 基于服务端:Websocket / Comet / SSE 等
对于非同源页面: