new WebSocket()可立即发起握手,但连接异步,readyState初始为0(CONNECTING),须监听onopen(readyState=1)后才能send(),否则报InvalidStateError;onerror不提供具体错误信息,需结合onclose及控制台排查。

直接用 new WebSocket() 就行,但必须处理好连接状态和错误
浏览器原生支持 WebSocket,不需要额外库。关键不是“能不能连”,而是“连上了没”“断了怎么办”“发出去没”。WebSocket 构造函数会立即发起握手,但连接是异步建立的,readyState 初始为 0(CONNECTING),不能一创建就调 send()。
常见错误现象:
- 调
ws.send()报错InvalidStateError: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state -
onopen没触发,但控制台也没报错——可能是服务端没响应 101 升级,或跨域/CORS 配置缺失 - 页面刷新后连接中断,但没主动
close(),导致服务端资源泄漏
实操建议:
- 始终在
onopen回调里发首条消息,或加if (ws.readyState === WebSocket.OPEN)安全判断 - 把
ws实例挂到全局或模块变量,避免被 GC 回收导致连接意外关闭 - 不要在
onerror里直接ws.close()——它可能根本没连上,close()会抛错;改用if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) ws.close()
onmessage 收到的是字符串,二进制数据要手动处理
除非服务端明确发送 ArrayBuffer 或 Blob,否则 event.data 默认是 string。很多新手误以为 JSON 自动解析,结果直接拿 event.data.xxx 报 undefined。
立即学习“前端免费学习笔记(深入)”;
使用场景:
- 纯文本消息(如聊天内容):直接用
event.data - 结构化数据(如 {“type”: “msg”, “payload”: …}):必须
JSON.parse(event.data) - 文件/音频流等二进制:需提前设置
ws.binaryType = 'arraybuffer',再用new Uint8Array(event.data)解析
容易踩的坑:
- 没加
try/catch包裹JSON.parse(),服务端偶尔发错格式就会让整个onmessage崩溃,后续消息全丢 - 混用
binaryType:设成'blob'后又按ArrayBuffer解析,读不到数据 - 大消息没做分片处理,单次接收超 10MB 触发浏览器内存限制(尤其移动端)
页面卸载前必须 close(),否则连接残留影响服务端
用户关标签页、刷新、跳转路由时,WebSocket 不会自动释放。服务端仍认为连接有效,持续占用 fd 和内存,积压多了直接 OOM。
实操建议:
- 绑定
window.onbeforeunload是底线,但不够可靠(比如安卓 Chrome 的后台进程可能杀掉 JS 上下文) - Vue/React 中,在组件
unmounted/useEffect cleanup里ws.close(),确保组件销毁即断连 - 生产环境建议加带码的关闭:
ws.close(1000, "Page unloaded"),服务端可区分是主动断开还是网络异常 - 如果用类封装 WebSocket(如
WebSocketService),务必暴露destroy()方法,且文档注明“必须手动调用”
别信“自动重连”,自己写才可控
原生 WebSocket 没有重连机制。网络抖动、休眠唤醒、WIFI 切换都会导致 onclose 触发,不处理就永远离线。
为什么简单轮询重连不行:
- 立刻重试:可能服务端还没恢复,徒增压力
- 固定间隔重试:体验差(5 秒才恢复)或浪费(1 秒内反复失败)
- 没限制重试次数:断网 10 分钟还在狂试,卡死 UI 线程
推荐做法(轻量级):
- 在
onclose里启动setTimeout,首次延迟 1s,失败后指数退避(2s → 4s → 8s) - 最多重试 5 次,之后暂停,等用户手动触发重连(比如点“重试”按钮)
- 记录重连次数和最后失败时间,避免页面长期后台运行时悄悄耗尽内存
- 重连前检查
navigator.onLine,断网时不白试
复杂点或容易被忽略的地方:重连时要重建整个 WebSocket 实例,不能复用旧的 ws 对象;且重连成功后,需重新注册所有 onmessage、onerror 回调——它们不会自动继承。


















