怎样排查“到底是谁改了这个对象”?
先理解原理和示例,再练习完整口述与追问。
回答要点
开发环境用 Proxy 拦截 set/deleteProperty 打印调用栈;单个属性可用访问器设置断点;Object.freeze 让严格模式下的修改直接抛错;DOM 用“属性修改时中断”;框架用 Vue 的 onTrigger 或 Redux DevTools 看是谁触发了变化。
原理拆解
修改对象本身不会留下记录,需要在写入发生的那一刻打断或记录调用栈。Proxy 可以拦截通过代理进行的写入和删除,在陷阱里 console.trace() 或写 debugger,调用栈就会指向修改者。陷阱内用 Reflect.set(target, key, value, receiver) 完成默认行为,它会正确处理继承来的 setter 和 this,并返回是否成功。
只关心某个属性时,可用 Object.defineProperty 把它改成访问器,在 setter 里打断点。Object.freeze 后,严格模式代码修改会在出错位置抛 TypeError,适合测试环境快速定位。Vue3 的 watch/computed 支持 onTrack、onTrigger 调试选项,Pinia 和 Redux 的 DevTools 能显示每次修改来自哪个 action。
示例与推演
function watchMutations(target, label = 'object') {
return new Proxy(target, {
set(obj, key, value, receiver) {
console.trace(`[${label}] set ${String(key)}`, value)
return Reflect.set(obj, key, value, receiver)
},
deleteProperty(obj, key) {
console.trace(`[${label}] delete ${String(key)}`)
return Reflect.deleteProperty(obj, key)
}
})
}
const config = watchMutations({ theme: 'light' }, 'config')
config.theme = 'dark' // 控制台打印修改位置的调用栈
易错点
Proxy 只能拦截经过代理引用的修改,别处仍持有原始对象引用时会绕过;嵌套对象需要递归包装。调试代码不要带上线,Proxy 和调用栈打印都有性能开销。
参考回答可直接练习口述
我会在写入发生时拿到调用栈。开发环境可以把对象包成 Proxy,在 set 和 deleteProperty 陷阱里打印 trace,再用 Reflect 完成默认写入;只盯一个属性就用访问器加断点;测试中也可以 freeze 对象,让修改处直接抛错。如果是框架状态,就用 Vue 的 onTrigger 或 Pinia、Redux DevTools 看修改来源。要注意 Proxy 只拦截通过代理的写入,原始引用会绕过。