Vue3用Proxy重写了响应式系统(Vue2用的是Object.defineProperty),这是一个高频面试题。
核心原理
Proxy是ES6提供的原生API,可以"拦截"对一个对象的各种操作(读取属性、修改属性、删除属性等),Vue3的响应式,本质上就是用Proxy包一层你的数据对象,当你读取/修改属性时,Vue能感知到,从而触发相应的更新。
简化版的核心逻辑长这样:
javascript
function reactive(target) {
return new Proxy(target, {
// 读取属性时触发
get(obj, key) {
track(obj, key); // "追踪依赖":记录"这个属性被谁用了"(比如被某个组件的渲染函数用了)
return objkey;
},
// 修改属性时触发
set(obj, key, value) {
objkey = value;
trigger(obj, key); // "触发更新":通知所有依赖这个属性的地方,"我变了,你们该更新了"
return true;
}
});
}
具体到实际使用:
javascript
const state = reactive({ count: 0 });
// 模板里用到了 state.count,相当于触发了 get,Vue记录下"这个视图依赖state.count"
console.log(state.count);
state.count++; // 触发了 set,Vue知道count变了,去通知所有依赖它的视图,触发重新渲染
为什么Vue3要从Object.defineProperty换成Proxy------这是最常见的追问
Vue2的Object.defineProperty有几个明显缺陷:
只能拦截"已经存在"的属性,如果你后来给对象新增一个属性,Vue2是感知不到的(这就是Vue2里为什么要用Vue.set()这种特殊API才能让新增属性变成响应式)
对数组的支持很差------defineProperty拦截不了数组的索引赋值(arr0 = xxx)和长度变化,Vue2要单独重写数组的push、pop等方法才能让这些操作触发响应式
需要递归遍历对象的每一个属性,提前用defineProperty一个个包裹,性能开销大,而且深层嵌套对象需要递归处理
Proxy的优势:
可以拦截整个对象,不管是已有属性还是新增属性,统一都能被拦截到,不需要Vue.set这种特殊API
对数组天然支持,索引赋值、length变化都能被正确拦截
可以做到懒递归(访问到深层属性时才递归处理,而不是一开始就全部遍历包裹),性能更好
总结:
"Vue3用Proxy重写响应式,核心是用Proxy包裹数据对象,拦截get和set操作,get的时候收集依赖(记录这个数据被哪些地方用到),set的时候触发更新(通知所有依赖它的地方重新渲染)。相比Vue2用的Object.defineProperty,Proxy能直接监听到属性的新增和删除,不需要Vue.set这种特殊API,对数组的支持也更好,因为defineProperty拦截不了数组索引赋值和length变化。"