如何利用单例模式与闭包确保单页应用全局仅有一个WebSocket连接实例 为何不能直接多次调用 new WebSocket() 在单页应用中,若每个页面或组件随意使用 new WebSocket(url),将导致多个物理连接同时存在。其后果是服务端会识别为多个独立客户端,资源占用倍增,消息广播也可能

在单页应用中,若每个页面或组件随意使用 new WebSocket(url),将导致多个物理连接同时存在。其后果是服务端会识别为多个独立客户端,资源占用倍增,消息广播也可能重复推送给同一用户。重连逻辑将变得难以统一管理。在 uni-app、Taro 等跨端框架中,情况更为复杂——new WebSocket() 这一原生 API 仅在 H5 环境有效,在小程序及 App 端并不可用。这意味着,直接使用该方式将破坏跨端行为的一致性。
长期稳定更新的攒劲资源: >>>点此立即查看<<<
核心思路是利用立即执行函数表达式(IIFE)将 instance 变量封闭在内部作用域,外部无法直接修改。只有首次调用 getInstance() 时才会真正创建连接实例:
const WebSocketSingleton = (function () {
let instance = null
return {
getInstance: function (url) {
if (!instance) {
// 此处需使用平台兼容的 API,例如 uni-app 应使用 uni.connectSocket
instance = {
url,
socket: null,
connect() {
uni.connectSocket({ url })
uni.onSocketOpen(() => { /* 保存引用 */ })
uni.onSocketMessage((e) => { /* 向监听者转发消息 */ })
},
send(data) { uni.sendSocketMessage({ data }) }
}
}
return instance
}
}
})()
WebSocketSingleton.getInstance('wss://...') 时,不会新建连接,而是直接返回已创建的 instance。url,闭包内的判断逻辑也不会覆盖原有实例。这虽可避免意外覆盖,但也可能带来隐患——若调用方传入不一致的 url,闭包可能误判为需要新建实例,导致意料之外的新连接。因此,最佳实践是确保所有调用传入的 url 完全一致。url 提取至统一配置层管理,而非在每次调用 getInstance 时动态传入。在 uni-app 生态中,uni.connectSocket 是官方提供的跨端统一 API,但其在不同平台的行为存在差异,使用时需注意:
onReady 生命周期之后。若在 onLoad 阶段调用,在 App 端很可能失败。wss://。小程序平台会强制校验,ws:// 协议仅可在 H5 环境下用于本地调试。encodeURIComponent 编码。例如参数 token=abc+def 若不编码,可能导致连接意外中断。uni.onSocketOpen 事件触发。否则直接调用发送接口会报错 fail websocket not connected。实现单例并不代表连接永久有效。实际场景中,应用切换至后台、网络切换或小程序被系统回收等,都会触发 uni.onSocketClose 事件。此时单例中的 instance.socket 可能仍不为 null,若后续直接调用 send 方法,将静默失败且难以追踪。
uni.onSocketClose 或 uni.onSocketError 事件触发后,必须手动清理状态,将 instance.socket 重置为 null。setTimeout。更健壮的做法是采用指数退避策略:例如首次重连等待 1 秒,第二次等待 3 秒,第三次等待 9 秒……通常最多尝试 5 次。onHide 生命周期中,应主动调用 uni.closeSocket() 并重置所有连接状态。否则用户切回应用时,连接可能已失效,但应用层仍显示“已连接”,导致后续操作全部失败。闭包单例模式仅保证了“实例的唯一性”。连接本身的活性维持、错误恢复以及与应用生命周期的同步,仍需开发者手动完善。遗漏这些关键逻辑,单例便只是一个看似整洁、实则脆弱的空壳。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述