Vue3响应式原理深度剖析:Proxy代理与reactive函数的底层实现

从Object.defineProperty到Proxy:Vue3响应式的架构演进

Vue2使用Object.defineProperty劫持对象属性的getter/setter,存在三个硬伤:无法检测属性新增和删除、无法监听数组索引和长度变化、深层嵌套对象需要递归代理导致初始化性能差。

Vue3改用ES6 Proxy重写响应式系统,从「属性劫持」升级为「对象代理」,一次代理覆盖所有属性操作,包括新增、删除、遍历和in操作符。核心优势:懒代理(深层属性访问时才递归)和全操作拦截。

reactive函数的源码执行路径

调用reactive(obj)时,内部执行流程:

// 简化版源码逻辑
const reactiveMap = new WeakMap()

function reactive(target) {
  // 1. 只代理对象类型
  if (typeof target !== 'object' || target === null) return target
  
  // 2. 已代理直接返回
  const existing = reactiveMap.get(target)
  if (existing) return existing
  
  // 3. 创建Proxy代理
  const proxy = new Proxy(target, mutableHandlers)
  reactiveMap.set(target, proxy)
  return proxy
}

核心在mutableHandlers,它定义了Proxy的四个拦截器:

  • get:读取时收集依赖(track),对嵌套对象懒代理
  • set:写入时触发更新(trigger),处理新增属性
  • deleteProperty:删除属性时触发更新
  • has:支持in操作符的响应式

依赖收集:track函数与effect副作用栈

Vue3维护一个全局副作用栈activeEffect,当组件渲染函数或watchEffect执行时,当前effect入栈:

let activeEffect = null
const targetMap = new WeakMap()  // target → Map(key → Set(effect))

function track(target, key) {
  if (!activeEffect) return
  
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  
  dep.add(activeEffect)      // 双向依赖
  activeEffect.deps.push(dep) // effect也记录dep,方便清理
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  
  const dep = depsMap.get(key)
  if (dep) {
    // 拷贝后遍历,防止无限循环
    const effectsToRun = new Set(dep)
    effectsToRun.forEach(effect => effect.run())
  }
}

依赖收集是O(1)操作,触发更新时遍历该key对应的所有effect执行。WeakMap以target为key保证对象销毁时依赖自动GC。

深层代理的懒执行机制

Vue2在初始化时递归遍历整个对象树做defineProperty,大对象启动慢。Vue3的Proxy采用懒代理策略:

function get(target, key, receiver) {
  const res = Reflect.get(target, key, receiver)
  
  // 收集依赖
  track(target, key)
  
  // 懒代理:只有访问到嵌套对象时才递归代理
  if (isObject(res)) {
    return reactive(res)
  }
  return res
}

reactive(res)内部会检查WeakMap,已代理的不会重复创建。首次访问state.user.profile时,逐层创建Proxy代理,未访问的嵌套对象零开销。

ref与reactive的设计取舍

reactive只接受对象,基本类型值无法Proxy代理。Vue3用ref解决:

function ref(value) {
  return new RefImpl(value)
}

class RefImpl {
  constructor(value) {
    this._value = isObject(value) ? reactive(value) : value
  }
  get value() {
    trackRefValue(this)
    return this._value
  }
  set value(newVal) {
    this._value = isObject(newVal) ? reactive(newVal) : newVal
    triggerRefValue(this)
  }
}

ref本质是一个带.value访问器的包装对象,内部对对象类型仍然调用reactive做Proxy代理。模板中自动解包(不需要写.value)是编译器层面的语法糖。

shallowReactive与性能优化场景

当确认不需要深层响应式时,用shallowReactive跳过嵌套代理:

const state = shallowReactive({
  list: largeArray,  // 不会递归代理数组元素
  meta: bigObject    // 不会递归代理meta内部属性
})

// 需要手动触发更新
state.list.push(newItem)
triggerRef(ref(state.list))  // 手动通知

适用场景:大型不可变数据(如表格数据源)、第三方库返回的对象、性能敏感的大列表渲染。配合markRaw标记跳过代理的对象,可完全避开Proxy开销。

响应式系统的设计哲学:默认安全(全代理),按需优化(shallow/markRaw)。理解底层机制后,性能调优和bug排查才能有的放矢。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-xiang-ying-shi-yuan-li-shen-du-pou-xi-proxy-dai-li-yu/

(0)
小编小编
上一篇 20小时前
下一篇 20小时前

相关推荐