单页应用中websocket必须采用组件级+页面级双保险关闭机制,因为刷新页面、关闭标签页等操作会直接终止js上下文,导致beforedestroy/onbeforeunmount钩子无法执行,仅依赖生命周期钩子会造成服务端堆积、内存泄漏和文件描述符耗尽。

单页应用中,组件销毁时 WebSocket 不能只靠 Vue 的生命周期钩子关闭——因为刷新页面、关闭标签页等操作会直接终止 JS 上下文,beforeDestroy 或 onBeforeUnmount 根本不会执行。必须用「组件级 + 页面级」双保险机制,才能真正释放连接、避免服务端堆积、内存泄漏和文件描述符耗尽。
为什么单靠生命周期钩子不行
Vue 的销毁钩子(如 beforeDestroy / onBeforeUnmount)只在组件被 Vue 主动卸载时触发,例如:
• 路由跳转离开当前页面
• v-if 切换为 false
• 动态组件被替换
但以下场景完全绕过这些钩子:
• 按 F5 或 Ctrl+R 刷新页面
• 直接关闭浏览器标签页
• 地址栏输入新 URL 后回车
• 浏览器进程被强制退出
此时 JS 执行环境瞬间销毁,所有监听、定时器、WebSocket 实例都来不及清理,服务端持续收包、客户端对象无法 GC,是典型资源泄漏根源。
必须同时绑定 beforeunload 和生命周期钩子
可靠方案是用同一套关闭逻辑,分别注册到两个事件源,并确保解绑干净:
递归分析 Vue 项目组件依赖,从入口文件生成组件层级图,支持 Vue 2/3,输出组件名、文件路径和属性。适用于分析组件结构、排查依赖或了解项目架构。
- 在 setup 或 created 中创建 WebSocket 实例,并用 ref / data 属性保存引用
- 立即用
window.addEventListener('beforeunload', closeWs)绑定页面级兜底 - 在 onBeforeUnmount / beforeDestroy 中调用
closeWs(),并同步移除beforeunload监听器 -
closeWs内部先判空,再调ws.close(1000, "Component unmounted"),最后置ws = null
推荐传关闭码和原因,配合服务端完成协议握手
调用 close() 时不传参数,服务端无法区分是异常断连还是主动退出。RFC 6455 要求发送标准关闭帧:
- 使用
ws.close(1000, "Page unloaded")或ws.close(1001, "User navigated away") - 状态码 1000 表示正常关闭,服务端应响应同码关闭帧,完成四次挥手
- 原因字符串建议控制在 123 字节内,避免截断
- 前端 onclose 回调中检查
event.wasClean === true,确认是否协议级成功关闭
关闭后还要清理关联资源
WebSocket 实例只是冰山一角,配套资源不清理照样泄漏:
- 清除心跳定时器(如
clearInterval(this.heartbeatTimer)) - 取消未完成的
send()调用(避免 InvalidStateError) - 移除所有自定义事件监听(
ws.removeEventListener) - 若使用 vue-native-websocket 插件,需调用
this.$socket.close()并清空全局实例引用 - 将 ws 引用设为 null,切断 GC 引用链,帮助内存回收
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










