关键词: 页面通讯
问题区:
从页面 A 打开一个新页面 B,B 页面关闭(包括意外崩溃),如何通知 A 页面
拆分问题:
- A页面打开B页面,A、B页面如何通讯
- B页面正常关闭,如何通知A页面
- B页面意外奔溃,又该如何通知A页面
A页面打开B页面,A B页面之间的通讯
A,B页面的通讯方式:
- URL 传值
- postmessage
- localStorage
- WebSocket
- SharedWorker
- Service Worker
url 传参
A页面 监听 window.addEventListener(‘hashchange’) 事件
注: window.open(‘a.html’, ‘A’) 的第二个参数,self 和 _blank 和 已经有名字的窗口打开
<!-- A.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>A</title>
</head>
<body>
<h1>A 页面</h1>
<button type="button" onclick="openB()">B</button>
<script>
window.name = 'A'
function openB() {
window.open("B.html", "B")
}
window.addEventListener('hashchange', function () {// 监听 hash
alert(window.location.hash)
}, false);
</script>
</body>
</html>
B页面 监听 window.onbeforeunload 事件
<!-- B.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>B</title>
<button type="button" onclick="sendA()">发送A页面消息</button>
</head>
<body>
<h1>B 页面</h1>
<span></span>
<script>
window.name = 'B'
window.onbeforeunload = function (e) {
window.open('A.html#close', "A")
return '确定离开此页吗?';
}
</script>
</body>
</html>
postmessage
postMessage
是h5引入的 API,postMessage()
方法允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档、多窗口、跨域消息传递,可在多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案,简直不要太好用
A 页面打开 B 页面,B 页面向 A 页面发送消息
A 页面打开,监听 message 事件
<!-- A.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>A</title>
</head>
<body>
<h1>A 页面</h1>
<button type="button" onclick="openB()">B</button>
<script>
window.name = "A"
function open () {
window.open('b.html', 'b')
}
window.addEventListener("message", receiveMessage, false)
function receiveMessage(e) {
console.log('收到消息', event.data)
}
</script>
</body>
</html>
B 页面 targetPage.postMessage
<!-- B.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>B</title>
<button type="button" onclick="sendA()">发送A页面消息</button>
</head>
<body>
<h1>B 页面</h1>
<span></span>
<script>
window.name = 'B'
function sendA() {
let targetWindow = window.opener
targetWindow.postMessage('Hello A', "http://localhost:3000");
}
</script>
</body>
</html>
localStorage
A 页面打开之后通过监听 window.addEvenetListener(“storage”, funciton(e) { e.data})
注意:localStorage 仅允许你访问一个Document 源(origin)的对象 Storage;存储的数据将保存在浏览器会话中。如果 A 打开的 B 页面和 A 是不同源,则无法访问同一 Storage
SharedWorker
SharedWorker
接口代表一种特定类型的 worker,可以从几个浏览上下文中访问,例如几个窗口、iframe 或其他 worker。它们实现一个不同于普通 worker 的接口,具有不同的全局作用域, SharedWorkerGlobalScope
。
// A.html
var sharedworker = new SharedWorker('worker.js')
sharedworker.port.start()
sharedworker.port.onmessage = evt => {
// evt.data
console.log(evt.data) // hello A
}
// B.html
var sharedworker = new SharedWorker('worker.js')
sharedworker.port.start()
sharedworker.port.postMessage('hello A')
// worker.js
const ports = []
onconnect = e => {
const port = e.ports[0]
ports.push(port)
port.onmessage = evt => {
ports.filter(v => v!== port) // 此处为了贴近其他方案的实现,剔除自己
.forEach(p => p.postMessage(evt.data))
}
}
Service Worker
Service Worker 是一个可以长期运行在后台的 Worker,能够实现与页面的双向通信。多页面共享间的 Service Worker 可以共享,将 Service Worker 作为消息的处理中心(中央站)即可实现广播效果。
// 注册
navigator.serviceWorker.register('./sw.js').then(function () {
console.log('Service Worker 注册成功');
})
// A
navigator.serviceWorker.addEventListener('message', function (e) {
console.log(e.data)
});
// B
navigator.serviceWorker.controller.postMessage('Hello A');
B 页面正常关闭,如何通知 A 页面
页面正常关闭时,会先执行window.onbeforeunload,然后执行window.onunload,我们可以在这两个方法里向 A 页面通信
B 页面意外崩溃,又该如何通知 A 页面
使用 ServiceWorker
完整设计一套流程
- B 页面加载后,通过 postMessage API 每 5s 给sw 发送一个心跳,表示自己在线,sw 将在线的网页登记下来,更新登记时间
- B 页面在 beforunload 时,通过 postMessage API 告诉自己已经正常关闭,sw 将登记的页面清除;
- A 页面 Sevice Worker 每 10S 查看一遍登记中的网页,发现登记时间已经超出一定是时间(比如 15s),即可判定该网页崩溃(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();
}
// A 页面
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]
}
})