在 Vue2 中,响应式系统是通过 Object.defineProperty 实现的,每个属性都通过 getter/setter 被拦截,从而实现依赖追踪和变更通知:
Object.defineProperty(obj, 'key', {
get() {
// 收集依赖
return value;
},
set(newVal) {
// 通知更新
value = newVal;
}
});
但defineProperty 有天然的缺陷:
问题 | 说明 |
---|---|
1. 无法监听新增属性 | 给对象新增的属性不会自动响应,需要 Vue.set。 |
2. 无法监听删除属性 | 删除属性也不会触发更新,需要手动触发。 |
3. 无法直接监听数组索引变化 | 修改数组索引无法被侦测,需要使用特定方法(如 Vue.set)。 |
4. 无法拦截整个对象 | 只能一开始递归遍历每一个属性,无法整体拦截对象。 |
5. 性能问题(大量数据) | 初始化时递归地遍历所有对象属性,成本很高。 |
为了弥补这些问题,会出现如Vue.set、Vue.delete这些方法来实现对新增和删除属性的监听,没办法,defineProperty从底层上就有这些限制,尤雨溪本人来也解决不了。
来,咱们一一对应看。
Vue2问题:
vm.obj.a = 123; // 不会响应式更新,必须 Vue.set(vm.obj, 'a', 123);
Vue3解决:
reactiveObj.a = 123; // 自动响应,无需手动 set!
因为 Proxy 能拦截 set 操作,即使属性原本不存在,也能监听到。
Vue2问题:
delete vm.obj.a; // 删除了但不会通知视图更新
Vue3解决:
delete reactiveObj.a; // 自动触发响应,页面同步更新!
因为 Proxy 支持拦截 deleteProperty 操作,天然监听删除。
Vue2问题:
vm.arr[2] = 10; // 不会响应,需要用 Vue.set(vm.arr, 2, 10)
Vue3解决:
reactiveArr[2] = 10; // 自动触发响应,刷新视图!
因为 Proxy 能拦截数组下标访问和修改,索引不再是问题。
Vue2问题:
初始化时,需要递归 walk 整棵数据树,开销大,尤其是深层嵌套对象或大数组时,性能堪忧。
Vue3解决:
只要一层 Proxy,就可以懒代理,当访问到某个子对象时,再进行子对象的代理(懒代理 Lazy Proxy)。
Vue2问题:
Vue3解决:
import { reactive } from 'vue';
const map = reactive(new Map());
map.set('key', 'value'); // 响应式
可以说,Proxy 是 Vue3 响应式革命的基石。Vue3 用 Proxy,完美解决了 Vue2 中 defineProperty 无法监听“新增属性、删除属性、数组索引、复杂数据结构”等一系列痛点,同时带来了更高效、更优雅、更强大的响应式体验。
只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。