过滤器函数内部修改传入对象会污染原数据,因对象和数组是引用传递;应避免在过滤器中排序、增删字段等操作,仅用于无副作用的格式化,必要时需浅拷贝或深拷贝输入值。

过滤器函数内部修改传入对象会污染原数据
uni-app 的过滤器本质是纯函数,但如果你在函数体里直接修改 value(比如 value.name = 'new' 或 value.push()),它就会真实改变原始数据——因为对象和数组是引用传递。这不是 Vue 的响应式机制问题,而是 JavaScript 语言特性导致的意外副作用。
- 常见错误场景:把后端返回的
list直接传给过滤器,过滤器里调用value.sort()或value.map(item => ({...item, id: item.id + '_temp'}))后再返回,结果所有页面共享的原始list被重排序或加了临时字段 - 只要
value是对象或数组类型,且你在过滤器里做了赋值、增删、排序等操作,就存在污染风险 - 字符串、数字、布尔值等原始类型不会被污染(它们是值传递),但实际业务中传进来的几乎全是对象数组
必须对引用类型做浅拷贝或深拷贝
过滤器不负责维护数据状态,只负责“输入→输出”,所以你得主动切断引用关系。具体选哪种拷贝方式,取决于数据结构复杂度:
- 简单对象(无嵌套、无 Date/RegExp/Function):
Object.assign({}, value)或{...value} - 简单数组(只有一层):
[...value]或value.slice() - 含嵌套对象或特殊类型(如后端返回带
created_at字符串的时间字段):用JSON.parse(JSON.stringify(value))(注意会丢掉函数、undefined、Symbol 和 Date 对象) - 更健壮的方案:在项目里统一引入
lodash.cloneDeep,但要注意小程序平台体积限制,避免只为一个过滤器引入整包
更推荐的做法是根本不在过滤器里处理结构变更
过滤器只做格式化、截断、映射这类无副作用的操作。如果逻辑涉及排序、筛选、字段增删,说明它已经越界了——这该由 computed、methods 或请求后的数据预处理承担。
- 例如搜索过滤:别写
{{ list | searchFilter(keyword) }},而是在computed里写filteredItems() { return this.list.filter(...); } - 例如状态映射:别在过滤器里把
1改成{ text: '已支付', color: 'green' },而是提前在data或onLoad中转换好,过滤器只负责{{ item.statusText | uppercase }} - 过滤器签名必须保持
(value, ...args)形式,且函数体内不能出现this、uni、await等上下文依赖项——否则跨平台时(尤其小程序)直接报错或静默失败
最容易被忽略的一点:filter 里调用 Date.parse 或 new Date 可能间接污染
你以为只是解析时间,但若传入的是对象(比如 { time: '2026-04-21' }),又没做类型判断就直接 value.time = new Date(value.time),那原始对象就被改了。真正安全的写法是先拷贝,再改拷贝体:
Vue.filter('formatTime', (value, key = 'time') => {
if (!value || typeof value !== 'object') return ''
const copy = { ...value } // 浅拷贝
if (copy[key]) {
copy[key] = new Date(copy[key]).toLocaleDateString('zh-CN')
}
return copy
})
不过这种需求本身就不该出现在过滤器里——它混淆了“数据加工”和“视图格式化”的边界。真要改字段,放在 methods 或请求回调里做一次性的数据清洗更清晰、可测、易调试。


















