uni.$on 必须配 uni.$off 且仅在 onunload 中调用,否则导致内存泄漏;需保存回调引用、避免重复注册;推荐使用自建 eventbus 替代,确保跨平台一致性与类型安全。

直接用 uni.$on + uni.$emit 不加清理,小程序端必 leak —— 页面跳走后监听器还在,回调里闭包持有的 this、data、refs 全部无法回收,真机跑两三个页面切换就明显卡顿。
uni.$on 必须配 uni.$off,且只能在 onUnload 里调用
小程序页面生命周期中,onUnload 是唯一可靠卸载时机;onHide 或 beforeDestroy 在小程序平台不触发或不可靠,尤其 tab 切换时 beforeDestroy 根本不执行。
-
uni.$on注册监听必须保存回调引用,不能传匿名函数(否则uni.$off无法精准移除) - 推荐写法:
const handleLogin = (data) => { ... };→uni.$on('login', handleLogin)→uni.$off('login', handleLogin) - 多个事件共用一个回调?不行。每个
uni.$on都要单独uni.$off,否则残留
跨页面通信场景下,避免重复注册同一事件
比如 A 页面跳 B 页面,B 页面又跳 C,C 页面再 uni.$on('dataUpdate') —— 如果没清理,A、B、C 三个页面的监听器全堆在事件中心里,一次 uni.$emit('dataUpdate') 就触发三次。
- 常见错误:在
onLoad里无条件uni.$on,没判断是否已注册 - 稳妥做法:用标志位控制,或封装一层防重注册逻辑(但不如直接依赖
onUnload清理来得干净) - 注意:小程序端
uni.$on不支持「覆盖式注册」,每次调用都新增监听器,不是替换
替代方案:纯 JS EventBus 更可控,但需手动管理生命周期
如果项目已用 TypeScript 或需要类型校验,建议弃用 uni.$on,改用自建 EventBus 工厂:
export class EventBus {
private handlers: Record<string array>> = {}
on(type: string, fn: Function) {
if (!this.handlers[type]) this.handlers[type] = []
this.handlers[type].push(fn)
}
emit(type: string, payload?: any) {
this.handlers[type]?.forEach(fn => fn(payload))
}
off(type: string, fn?: Function) {
if (!fn) {
delete this.handlers[type]
return
}
this.handlers[type] = this.handlers[type]?.filter(f => f !== fn) || []
}
}</string>
- 实例挂全局(如
globalThis.eventBus),所有页面 import 后直接用 - 仍需在
onUnload调eventBus.off('xxx', callback),不写照样 leak - 优势:可加泛型、可单元测试、不依赖 uni-app 内部实现,H5/App 小程序行为一致
真正容易被忽略的点是:小程序没有组件级自动销毁机制,所有事件监听、定时器、蓝牙监听、WebSocket 连接,只要没显式 off/close/clearInterval,就永远活着 —— 它们不随页面隐藏而暂停,也不随路由跳转而释放。











