Proxy的基本用法很简单:通过new Proxy(target, handler)创建一个代理对象,handler里定义各种陷阱函数。举个例子,假设我们有个user对象,用Proxy包装后,任何访问或修改都会触发handler里的逻辑。例如,在get陷阱中,我们可以收集依赖;在set陷阱中,我们通知更新。这种设计让响应式系统更灵活,能自动处理新增属性,无需手动干预。
但光有Proxy还不够,Vue3还结合了Reflect来保证操作的默认行为。Reflect是ES6另一个内置对象,它提供了与Proxy陷阱对应的方法,比如Reflect.get和Reflect.set。在Proxy的handler里,我们通常会调用Reflect来执行原始操作,这样可以避免破坏对象的原生行为。比如,在set陷阱中,先调用Reflect.set完成赋值,再触发更新通知,确保了数据修改的准确性。
在实际编码中,我们可以模拟一个简易响应式系统。下面是一个基础实现:先定义一个reactive函数,用Proxy包装对象,在get中通过track函数收集依赖(比如当前正在执行的effect),在set中通过trigger函数触发依赖更新。代码大致如下:
javascript复制下载const targetMap = new WeakMap();
let activeEffect = null;
function track(target, key) {
if (activeEffect) {
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
}
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect());
}
}
function reactive(target) {
return new Proxy(target, {
get(obj, key, receiver) {
track(obj, key);
return Reflect.get(obj, key, receiver);
},
set(obj, key, value, receiver) {
const result = Reflect.set(obj, key, value, receiver);
trigger(obj, key);
return result;
}
});
}这个例子中,我们用WeakMap存储依赖关系,避免内存泄漏。activeEffect全局变量指向当前运行的副作用函数,通过effect函数来注册。当代理对象属性被读取时,track会记录依赖;被修改时,trigger会执行所有关联的effect。这种模式正是Vue3响应式的核心,只不过Vue3的实现更复杂,支持了computed、watch等高级功能。
Proxy的优势不仅在于功能全面,还提升了性能。由于它能直接拦截整个对象,无需递归初始化,大大减少了初始渲染的开销。在我的项目中,改用Vue3后,列表渲染的帧率稳定了很多。另外,Proxy对数组的处理也更自然,比如push、pop操作都能被拦截,不再需要重写数组方法。
当然,Proxy也有注意事项。比如,它只能代理对象,对原始值无效,所以Vue3引入了ref来处理基本类型。另外,浏览器兼容性虽不是大问题(现代浏览器都支持),但在旧环境中可能需要polyfill。
总结来说,Vue3的响应式原理通过Proxy和Reflect的协同,实现了更高效、更灵活的依赖追踪。在实际开发中,理解这套机制能帮助我们优化性能,避免常见坑点。如果你也在用Vue3,不妨多试试自定义响应式对象,体验下这种“劫持”艺术的魅力。
1191

被折叠的 条评论
为什么被折叠?



