vue 3 响应式系统通过 weakmap 缓存和 reactiveflags 标记双重防护避免重复代理:缓存复用已创建的 proxy,标记校验对象类型(如是否已响应式、只读或原始目标),确保嵌套访问安全高效。

Vue 3 的响应式系统不会对同一个原始对象反复创建 Proxy,核心靠两层防御:缓存机制 + ReactiveFlags 标记校验。前者解决“已代理过”的问题,后者解决“不该再代理”的问题——比如只读代理、原始对象跳过标记、甚至已被代理过的嵌套子对象。
缓存层:WeakMap 记录原始对象与代理的映射
Vue 内部维护一个全局 reactiveMap: WeakMap,所有调用 reactive() 的对象都会先查这个缓存:
- 若
reactiveMap.has(target)为 true,直接返回已存在的 proxy,不新建 - 代理创建完成后,立刻执行
reactiveMap.set(target, proxy),完成缓存写入 - 使用 WeakMap 而非 Map,避免内存泄漏:当原始对象不可达时,缓存条目可被自动回收
标记层:ReactiveFlags 提供快速类型识别
ReactiveFlags 是一组 Symbol 常量(如 IS_REACTIVE、IS_READONLY、RAW),它们被写入 proxy 对象自身(不可枚举、不可配置),用于运行时快速判断对象性质:
-
target[ReactiveFlags.IS_REACTIVE]为 true → 表明该 target 已是 reactive 代理,无需再套一层 -
target[ReactiveFlags.IS_READONLY]为 true →reactive()直接返回原 target,防止把只读代理转成可写代理 -
target[ReactiveFlags.RAW]存在 → 说明该对象本身就是某个 proxy 的原始目标(即toRaw(proxy)的结果),应跳过代理 -
target[ReactiveFlags.SKIP]为 true → 用户手动标记此对象不参与响应式转换(如markRaw()处理过的实例)
嵌套访问时的双重防护
当访问 state.user.profile.name 时,user 和 profile 都会触发 get 拦截器。此时每一步都同时启用上述两层检查:
- 先查
reactiveMap:若user已代理过,直接复用 - 再查
user[ReactiveFlags.IS_REACTIVE]:若为 true,确认它是合法响应式对象,才继续对其属性值做递归reactive() - 若
user是个普通对象(无任何 flag),则走正常代理流程;若是只读代理,则跳过递归,避免降级
为什么不能只靠 instanceof 或 constructor?
Proxy 实例无法通过 instanceof Proxy 判断,也没有统一 constructor;而 Object.getPrototypeOf(proxy) === Object.prototype 也恒成立,无法区分普通对象与代理。ReactiveFlags 提供了唯一可靠、零误判的运行时标识方式——它由 Vue 内部写入,且仅在真正完成代理后才设置,语义明确、性能极佳。











