关键词: 页面通讯
问题区:
从页面 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 () {// 监听 hashalert(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.openertargetWindow.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.htmlvar sharedworker = new SharedWorker('worker.js')sharedworker.port.start()sharedworker.port.onmessage = evt => {// evt.dataconsole.log(evt.data) // hello A}// B.htmlvar sharedworker = new SharedWorker('worker.js')sharedworker.port.start()sharedworker.port.postMessage('hello A')// worker.jsconst 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 注册成功');})// Anavigator.serviceWorker.addEventListener('message', function (e) {console.log(e.data)});// Bnavigator.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页面会话的唯一 idlet 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 * 1000const CRASH_THRESHOLD = 15 * 1000const pages = {}let timerfunction checkCrash() {const now = Date.now()for (var id in pages) {let page = pages[id]if ((now - page.t) > CRASH_THRESHOLD) {// 上报 crashdelete 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]}})
