过滤器在 vue 2/uni-app 中无缓存、每次渲染都执行,优化只有提前计算或绕开模板调用;高频使用会阻塞 ui 线程,应移至 data/computed 初始化或用 watch + 工具函数替代。

过滤器在模板中每次渲染都会重新执行,没有缓存机制——这不是 bug,是 Vue 2(uni-app 当前运行时)的设计事实。想靠 v-memo 或类似机制拦截它?不行。真正有效的优化路径只有两条:提前计算,或绕开模板调用。
为什么过滤器高频调用必然拖慢长列表
每个 {{ item.content | truncate | highlight }} 在 v-for 渲染 100 条时,就执行 200 次函数;若其中含正则匹配、JSON.parse 或循环测量宽度,CPU 时间直接线性增长。更隐蔽的问题是:这些调用发生在视图层更新阶段,会阻塞 UI 线程,尤其在低端 Android 小程序里,滚动掉帧就是这么来的。
- 过滤器无法被 Vue 的响应式系统追踪依赖,所以 computed 无法替代它做缓存
-
data初始化时调用过滤器生成结果,比模板里实时调用快 3–5 倍(实测 iOS 微信小程序) - 哪怕只是
String.prototype.slice(0, 80),在 500+ 条 v-for 中也比提前截好慢 12%~18%
把过滤逻辑移到 data/computed 初始化阶段
别让模板承担“计算”职责,只负责“展示”。适用于内容稳定、不随用户交互实时变化的场景(如文章列表摘要、状态文案、时间格式化)。
- 在
onLoad或created钩子中,对原始数组做一次映射:this.list = res.data.map(item => ({ ...item, summary: this.$filters.truncate(item.content) })) - 如果需响应式更新(比如切换语言后重格式化),把过滤器封装成方法,放在
computed里返回新数组,而非在模板中链式调用 - 注意:不要在
computed里直接调用this.$filters.xxx()——它不是响应式函数,且 this 上下文可能丢失;应显式 import 过滤器函数或复用工具函数
用工具函数 + watch 替代模板过滤器链
当过滤逻辑复杂、参数动态、或需异步准备(如 canvas 测宽),硬塞进模板只会让问题更难定位。此时应放弃过滤器语法,改用可控的数据流。
- 定义纯函数工具(如
truncateByWidth(text, maxWidth)),放在utils/下统一管理,避免重复实现 - 用
watch监听原始数据变化,触发工具函数并更新中间字段:watch: { rawList: { handler() { this.processedList = this.rawList.map(truncateByWidth) }, immediate: true } } - 模板中直接绑定
{{ item.truncated }},彻底脱离|语法,也规避了 null/undefined 透传导致后续过滤器报错的风险 - 若涉及 canvas 测量,必须确保在
onReady后执行,否则uni.createCanvasContext返回空上下文
哪些情况仍可保留模板过滤器(但要加限制)
不是所有过滤器都该被干掉。极轻量、无副作用、参数固定的格式化操作,留着反而更清晰。关键是要划清边界:
- 仅限单参数、无内部状态、执行耗时 formatDate(timestamp)、
toUpper(str) - 禁止在过滤器内调用
uni.getSystemInfo、uni.createSelectorQuery、JSON.stringify等高开销操作 - 禁止在
v-for内使用带参数的过滤器(如{{ item | withSuffix(suffix) }}),参数应提前合并进数据对象 - 真机调试时,在开发者工具「性能」面板勾选 “JS Profile”,看
filterXXX是否频繁出现在 top functions 列表中——出现即说明该砍
最常被忽略的一点:很多人以为“我用了 computed 就安全了”,但若 computed 内部又调用了未缓存的过滤器,等于白搭。优化的本质不是换写法,而是把不可控的模板计算,变成可控的、可测的、可提前执行的数据预处理。











