不加兜底逻辑的 beforedestroy 或 onbeforeunmount 关闭 websocket 无法覆盖刷新、关页、地址栏回车等浏览器强制销毁场景,因这些操作会直接终止 js 上下文,导致钩子根本不会执行,连接残留、服务端持续收包、内存泄漏成为常态。

不加兜底逻辑的 beforeDestroy 或 onBeforeUnmount 关闭 WebSocket,大概率会漏掉刷新、关页、地址栏回车等场景,连接残留是常态,不是偶发。
Vue2 中只靠 beforeDestroy 关 WebSocket 为什么不行
这个钩子只在 Vue 主动卸载组件时触发(比如路由跳转、v-if 变 false),但用户按 F5、关标签页、直接输新 URL 回车——浏览器会立刻销毁 JS 上下文,beforeDestroy 根本没机会执行。
常见错误现象:
-
onclose日志完全不打印,但服务端持续收到心跳包 - 反复刷新后,服务端连接数只增不减,甚至触发文件描述符耗尽
- DevTools Memory 面板里能看到
WebSocket对象一直存活
这不是 Vue 的 bug,是浏览器机制决定的。它只管「组件级销毁路径」,不管「进程级强制终止」。
Vue3 Composition API 下必须用 onBeforeUnmount + beforeunload 双保险
单靠 onBeforeUnmount 同样覆盖不了页面级退出场景,必须补上原生 beforeunload 事件监听。
实操要点:
- 在
setup或<script setup></script>中创建WebSocket实例,并用ref或let保存引用 - 立即注册
window.addEventListener('beforeunload', closeWs),注意绑定方式(推荐箭头函数或命名函数) - 在
onBeforeUnmount中调用同一closeWs方法,并移除beforeunload监听器(避免跨组件污染) -
closeWs内部先判空再调ws.close(),然后置ws = null
示例片段:
import { onBeforeUnmount, ref } from 'vue'
const ws = ref(null)
// 创建连接
ws.value = new WebSocket('ws://example.com')
const closeWs = () => {
if (ws.value) {
ws.value.close()
ws.value = null
}
}
// 页面级兜底
window.addEventListener('beforeunload', closeWs)
onBeforeUnmount(() => {
closeWs()
window.removeEventListener('beforeunload', closeWs)
})
使用 vue-native-websocket 插件时怎么关
插件封装了全局连接,关闭逻辑和原生不同,不能只调 this.$socket.close()。
必须做两件事:
- 调用
this.$socket.close()发送关闭帧 - 紧接着调用
this.$socket.destroy()清理内部事件监听器、取消重连逻辑、释放引用
否则插件内部状态仍可能残留,导致后续重复初始化失败或内存泄漏。
正确写法(Options API):
beforeUnmount() {
if (this.$socket) {
this.$socket.close()
this.$socket.destroy()
}
}
容易被忽略的关键点
WebSocket 实例本身没有自动 GC 保障:即使连接已断,只要 JS 里还持有引用(比如没置 null),对象就不会被回收。
还有几个硬伤常被跳过:
- 没移除
onmessage/onerror等事件监听器(虽然关闭后它们不再触发,但引用链仍在) - 用
setTimeout或setInterval做心跳,却没在关闭时clearTimeout/clearInterval - 多个组件共用一个 WebSocket 实例,但只在一个组件里关,其他组件还在发消息
最稳妥的做法是:关连接 → 清监听 → 清定时器 → 置空引用 → 移除全局事件监听器。少一环,就可能埋下泄漏隐患。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











