uni-app中v-for配合过滤器易致内存泄漏,因过滤器若含闭包、定时器、事件监听或引用响应式对象且未清理,会阻止GC;应改用computed或无状态工具函数。

uni-app中v-for + 过滤器函数导致的内存泄漏
直接在模板里用过滤器(如 {{ item | formatTime }})本身不会泄漏,但若过滤器内部创建了闭包、定时器、事件监听或引用了外部响应式对象,且未随组件销毁清理,就会持续持有引用,阻止GC回收。
常见错误是把过滤器写成带状态的函数——比如缓存计算结果、内部启动setInterval、或监听全局事件。Uni-app 的 Vue2 兼容层(尤其 H5 和 App 端)对这类闭包引用的回收并不 robust。
- 避免在过滤器中访问
this或组件实例(过滤器是纯函数,this指向undefined,但开发者常误用箭头函数捕获上下文) - 禁止在过滤器内调用
uni.$on、addEventListener、setTimeout等副作用操作 - 不要返回包含闭包的对象(例如
return { value, update: () => {...} }),这会让整个作用域链无法释放
替代方案:用计算属性或computed代替模板过滤器
Vue2 模板过滤器无法响应生命周期,而 computed 属性天然绑定组件销毁逻辑,且可被 Vue 的依赖追踪系统管理。
例如,将时间格式化从过滤器迁移到 computed:
// ❌ 危险:过滤器中隐式持有了 this 或外部变量
filters: {
formatTime(timestamp) {
// 若这里用了闭包缓存、或调用了 this.$store,就可能泄漏
return dayjs(timestamp).format('YYYY-MM-DD')
}
}
// ✅ 安全:改用 computed,且只依赖 props 或 data
computed: {
formattedList() {
return this.rawList.map(item =>
dayjs(item.time).format('YYYY-MM-DD')
)
}
}
-
computed的 getter 在组件卸载时自动失效,依赖的响应式数据不再被追踪 - 如果必须复用格式化逻辑,抽成独立工具函数(无状态、无副作用),而非挂载到
filters或methods - 注意:H5 端 Vue2 的
computed有缓存机制;App 端(基于 WebView)同样适用,但小程序端需确认基础库版本 ≥ 2.20.0,否则computed在某些场景下不触发更新
使用watch + 手动清理时的典型陷阱
有些开发者为“优化性能”,在 mounted 中用 watch 监听数据并缓存格式化结果,却忘了在 beforeDestroy(或 onUnmounted)中取消监听——这会导致 watcher 持有组件引用,形成泄漏。
-
watch返回的取消函数必须显式调用,不能只存变量不执行 - 组合式 API 中,
onUnmounted必须与watch在同一作用域声明,否则闭包捕获不到 cancel 函数 - 避免在
watch回调里再创建新定时器或事件监听(二次嵌套闭包更难清理)
示例:
setup() {
const stopWatch = watch(() => props.list, (newVal) => {
// 格式化逻辑
})
onUnmounted(() => {
stopWatch() // 必须调用
})
}
小程序平台特别注意:filter 在微信小程序编译期被转译为setData路径
微信小程序端,uni-app 会把模板中的过滤器表达式(如 {{ item.name | upper }})编译进 setData 路径。若过滤器返回对象或深层嵌套结构,可能导致 setData 数据量暴增,间接引发内存压力甚至 OOM(尤其低端安卓机)。
- 过滤器返回值应尽量扁平(字符串、数字),避免返回数组、对象或函数
- 不要在过滤器里做深克隆、JSON.stringify 或正则全局匹配(
/g),这些操作在高频v-for中极易拖慢渲染线程 - 真机测试时,用
performance.memory.usedJSHeapSize对比前后差值,确认是否由过滤器引发内存增长
真正棘手的点往往不在“写了过滤器”,而在于它悄悄成了状态锚点——一个本该无状态的转换函数,因为开发时图方便加了缓存、监听或异步逻辑,最终变成内存里的钉子户。


















