-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
第169题:从页面 A 打开一个新页面 B,B 页面关闭(包括意外崩溃),如何通知 A 页面? #466
Comments
postmassage消息通信 |
b.unload |
本题是 html 页面通信题,可以拆分成:
A 页面打开 B 页面,A、B 页面通信方式据我所知,A、B 页面通信方式有:
url 传参url 传参数没什么可说的 <!-- 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: <!-- 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> A 页面通过 url 传递参数与 B 页面通信,同样通过监听 hashchange 事件,在页面 B 关闭时与 A 通信 postmessage
A 页面打开 B 页面,B 页面向 A 页面发送消息: <!-- 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?code=123", "B")
}
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
console.log('收到消息:', event.data)
}
</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>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
localStorage.setItem('testB', 'sisterAn');
// B
let testB = localStorage.getItem('testB');
console.log(testB)
// sisterAn 注意: WebSocket基于服务端的页面通信方式,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种 SharedWorker
// 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 WorkerService 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 页面页面正常关闭时,会先执行 B 页面意外崩溃,又该如何通知 A 页面页面正常关闭,我们有相关的 API,崩溃就不一样了,页面看不见了,JS 都不运行了,那还有什么办法可以获取B页面的崩溃? 全网搜索了一下,发现我们可以利用 window 对象的 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'));
} 使用 load 和 beforeunload 事件实现崩溃监控过程如下: 图片来自:https://zhuanlan.zhihu.com/p/40273861 这个方案巧妙的利用了页面崩溃无法触发 beforeunload 事件来实现的。 在页面加载时(load 事件)在 sessionStorage 记录 good_exit 状态为 pending,如果用户正常退出(beforeunload 事件)状态改为 true,如果 crash 了,状态依然为 pending,在用户第2次访问网页的时候(第2个load事件),查看 good_exit 的状态,如果仍然是 pending 就是可以断定上次访问网页崩溃了! 但有一个问题,本例中用 sessionStorage 保存状态,在用户关闭了B页面,sessionStorage 值就会丢失,所以换种方式,使用 Service Worker 来实现:
基于以上几点优势,完整设计一套流程如下:
代码如下: // 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]
}
}) 参考: |
No description provided.
The text was updated successfully, but these errors were encountered: