背景
对于我们开发者而言,如果访客登录了我们网站,但是登录之后整个页面崩溃了,这不仅让访客失去对页面的信任,也造成了公司经济流失,对此我总结了以下浏览器崩溃的原因:
-
内存泄漏。这是我在一次研发three项目时候遇到的,three显示三维场景需要初始化场景、摄像机还有渲染器,不仅如此,想要看到物体还得添加模型,如果模型多而且复杂多样,就会增加内存负担,而当我们退出三维场景页面时候,three是不会自主进行模型内存回收的,这也意味着你每访问一次,内存就会堆叠,频繁访问之后就会崩溃。
-
项目内部存在无法跳出的循环。如果我们常用while语句就更需要注意了,无限循环的js代码直接占用浏览器主线程,导致浏览器崩溃。(这我就不放出图片了,尽管关闭就没事了,但仍然觉得膈应)
-
项目代码复杂多样。如果一个网站打开需要进行大量循环,并且渲染的数据非常多,就会非常卡顿,或者是网站代码质量低,占用了许多内存,如果这时候再遇上一些比较老旧的浏览器,就雪上加霜了。
那么我们该如何监控到浏览器崩溃,及时换回损失呢?浏览器崩溃,那也就意味着主线程无法运行,在主线程添加监控代码也就不现实了,那么我们该如何脱离主线程监控到崩溃呢?
基于service worker的心跳检测
service worker在浏览器中是有自己独立的线程的,浏览器崩溃后,service worker所在的线程是不受影响的,并且它与主线程是可以通过postMessage发送事件与service worker建立联系。worker我相信大家都见过,但service worker是跟普通的worker是有区别的,用new Worker传入的js文件会在当前页面标签关闭后结束,而使用service worker只会在浏览器关闭后才结束执行,对于一些客户,页面崩溃也只会关闭当前标签,所以service woker生命周期更长,可以更好的检测页面状态。 基本思想如下:
- 用户打开网页后,主线程开启定时器,每隔5s给service worker发送一次信号。主线程能够发送信号是可以证明主线程还在正常运行,没有出现页面崩溃。
- service worker也开启定时器,每隔7s(至少比5s长)向后端发送崩溃信号,并且根据主线程发来的心跳信号,更新定时器,避免发送错误消息。 页面开始之后会发生两种情况,页面崩溃或者用户正常关闭。
- 页面崩溃之后主线程不再发送心跳信号,那么service worker线程内的定时器得不到更新,就会在时间结束后发送日志给后端,上报崩溃情况。
- 页面正常关闭需要我们发送页面关闭信号来关闭service worker线程的定时器,防止发送错误消息。 整体代码如下所示:
主线程:
//监控页面崩溃
export function init_track_crash() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('../work.js');
let heartbeat = function () {
navigator.serviceWorker.controller.postMessage(true)
}
heartbeat()
setInterval(heartbeat, 3000);
window.addEventListener('unload',()=>{
navigator.serviceWorker.controller.postMessage(false)
})
}
}
service worker线程:
let check_timer
onmessage = (e) => {
const { data } = e
if (data) {
if (check_timer) clearTimeout(check_timer)
check_timer = setTimeout(() => {
fetch("http://localhost:80/performance/crash", {
method: "POST",
body: JSON.stringify({
kind: "performance",
type: "crash",
startTime: Date.now()
}),
headers: {
'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded',
}
})
}, 5000);
}else{
if (check_timer) clearTimeout(check_timer)
}
}
注意点:
- service worker是浏览器关闭后才消失的,所以每次测试最好关闭浏览器,不然浏览器内部还是原来的文件。
- 如果你是Vue项目,你的service worker文件需要放到这里,并引用本地路径获取,如下: