返回首页 Vue3 源码

Vue3 响应式系统原理深度解析

Vue3 的响应式系统是框架的核心,相比 Vue2 发生了根本性的变化。本文将从源码层面深入解析 Vue3 响应式系统的实现原理,包括 Proxy 的使用、依赖收集机制、副作用函数调度等核心概念。


从 Object.defineProperty 到 Proxy

Vue2 使用 Object.defineProperty 实现响应式,但存在一些限制:

// Vue2 的限制
const vm = new Vue({
  data: {
    obj: { a: 1 }
  }
})

// ❌ 无法检测对象属性的添加
vm.obj.b = 2  // 无响应

// ❌ 无法检测数组索引和长度的变化
vm.arr[0] = 'new'  // 无响应
vm.arr.length = 0  // 无响应

Vue3 使用 Proxy 彻底解决了这些问题:

// Proxy 基本用法
const target = { a: 1 }
const proxy = new Proxy(target, {
  get(target, key, receiver) {
    console.log(`get ${key}`)
    return Reflect.get(target, key, receiver)
  },
  set(target, key, value, receiver) {
    console.log(`set ${key} = ${value}`)
    return Reflect.set(target, key, value, receiver)
  }
})

proxy.a   // get a
proxy.a = 2  // set a = 2
"Proxy 的核心优势是可以拦截对象的任何操作,包括属性访问、赋值、删除、枚举等。这让 Vue3 能够实现真正完整的响应式系统。"

reactive 实现原理

让我们手写一个简化版的 reactive

let activeEffect = null
const targetMap = new WeakMap()

// 依赖收集
function track(target, key) {
  if (!activeEffect) return

  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(target, key, receiver) {
      const result = Reflect.get(target, key, receiver)

      // 收集依赖
      track(target, key)

      // 如果是对象,递归代理
      if (typeof result === 'object' && result !== null) {
        return reactive(result)
      }

      return result
    },

    set(target, key, value, receiver) {
      const oldValue = target[key]
      const result = Reflect.set(target, key, value, receiver)

      // 值发生变化才触发
      if (oldValue !== value) {
        trigger(target, key)
      }

      return result
    }
  })
}

依赖收集的数据结构

// WeakMap
// WeakMap<Targetlt;Target, Map<Key, Set<Effect>>>
//
// 示例:
// targetMap = {
//   obj1: Map {
//     'name': Set [effect1, effect2],
//     'age': Set [effect1, effect3]
//   },
//   obj2: Map {
//     'count': Set [effect2]
//   }
// }

ref 实现原理

ref 用于包装基本类型值,使其成为响应式:

class RefImpl {
  constructor(value) {
    this._value = toReactive(value)
    this.__v_isRef = true
    this.dep = new Set()
  }

  get value() {
    // 收集依赖
    if (activeEffect) {
      this.dep.add(activeEffect)
    }
    return this._value
  }

  set value(newValue) {
    if (this._value !== newValue) {
      this._value = toReactive(newValue)
      // 触发更新
      this.dep.forEach(effect => effect())
    }
  }
}

function toReactive(value) {
  if (typeof value === 'object' && value !== null) {
    return reactive(value)
  }
  return value
}

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

effect 副作用函数

effect 是响应式系统的核心,用于包装副作用函数:

function effect(fn, options = {}) {
  const effectFn = () => {
    try {
      activeEffect = effectFn
      // 执行函数,触发 getter,收集依赖
      return fn()
    } finally {
      activeEffect = null
    }
  }

  effectFn.options = options
  effectFn.deps = []  // 存储依赖关系

  // 立即执行
  if (!options.lazy) {
    effectFn()
  }

  return effectFn
}

使用示例

const state = reactive({ count: 0 })

effect(() => {
  console.log(`count is: ${state.count}`)
})

state.count++  // 输出: count is: 1

依赖收集与触发详解

完整的 track 实现

function track(target, key) {
  if (!activeEffect) return

  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()))
  }

  // 双向绑定:effect 记录依赖的 key
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect)
    activeEffect.deps.push(dep)
  }
}

完整的 trigger 实现

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return

  const dep = depsMap.get(key)
  if (!dep) return

  // 复制一份避免在遍历时修改
  const effects = [...dep]

  effects.forEach(effect => {
    // 避免无限循环
    if (effect !== activeEffect) {
      if (effect.options.scheduler) {
        effect.options.scheduler(effect)
      } else {
        effect()
      }
    }
  })
}

computed 计算属性

computed 本质上是一个特殊的 effect,具有懒执行和缓存特性:

class ComputedRefImpl {
  constructor(getter) {
    this._value = undefined
    this._dirty = true  // 是否需要重新计算
    this._getter = getter
    this.dep = new Set()

    // 创建 effect,但懒执行
    this.effect = effect(getter, {
      lazy: true,
      scheduler: () => {
        if (!this._dirty) {
          this._dirty = true
          // 计算属性变化,触发依赖更新
          trigger(this, 'value')
        }
      }
    })
  }

  get value() {
    // 收集依赖
    if (activeEffect) {
      this.dep.add(activeEffect)
    }

    // 懒执行 + 缓存
    if (this._dirty) {
      this._value = this.effect()
      this._dirty = false
    }

    return this._value
  }
}

function computed(getter) {
  return new ComputedRefImpl(getter)
}

使用示例

const state = reactive({ count: 0 })

const doubled = computed(() => state.count * 2)

console.log(doubled.value)  // 0

state.count++  // 不会立即计算

console.log(doubled.value)  // 2 (访问时才计算)
console.log(doubled.value)  // 2 (使用缓存)

watch 侦听器原理

watch 是基于 effect 实现的:

// 简化版 watch 实现
function watch(source, cb, options = {}) {
  let getter
  let oldValue

  // 确定 getter
  if (typeof source === 'function') {
    getter = source
  } else {
    getter = () => traverse(source)
  }

  // 创建 effect
  const effectFn = effect(() => getter(), {
    lazy: true,
    scheduler: () => {
      // 在调度器中执行回调
      const newValue = effectFn()
      if (newValue !== oldValue) {
        cb(newValue, oldValue)
        oldValue = newValue
      }
    }
  })

  // 首次执行
  if (options.immediate) {
    oldValue = effectFn()
    cb(oldValue, undefined)
  } else {
    oldValue = effectFn()
  }
}

// 遍历对象,触发所有属性的 getter
function traverse(value) {
  if (typeof value !== 'object' || value === null) {
    return value
  }

  if (Array.isArray(value)) {
    for (let i = 0; i < value.length; i++) {
      traverse(value[i])
    }
  } else {
    for (const key in value) {
      traverse(value[key])
    }
  }

  return value
}

调度器与批处理

Vue3 使用调度器实现批处理,避免多次重复渲染:

// 调度队列
let queue = []
let isFlushing = false

function queueJob(job) {
  if (!queue.includes(job)) {
    queue.push(job)
  }

  if (!isFlushing) {
    isFlushing = true
    Promise.resolve().then(flushJobs)
  }
}

function flushJobs() {
  isFlushing = false

  // 排序:确保父组件先于子组件更新
  queue.sort((a, b) => a.id - b.id)

  try {
    for (let i = 0; i < queue.length; i++) {
      queue[i]()
    }
  } finally {
    queue.length = 0
  }
}

// 在 effect 中使用调度器
effect(() => {
  console.log(state.count)
}, {
  scheduler: (effect) => {
    queueJob(effect)
  }
})

批处理示例

const state = reactive({ count: 0 })

effect(() => {
  console.log(`count: ${state.count}`)
}, {
  scheduler: (fn) => queueJob(fn)
})

// 多次更新只会触发一次
state.count++
state.count++
state.count++
// 输出: count: 3

总结

Vue3 的响应式系统是一个精妙的设计,核心思想是通过 Proxy 拦截对象操作,结合 WeakMap、Map、Set 数据结构实现依赖收集和更新触发。

核心概念

  • Proxy:拦截对象操作,实现响应式基础
  • effect:包装副作用函数,实现依赖收集
  • track/trigger:依赖收集和更新触发机制
  • WeakMap:存储对象与依赖的关系,支持垃圾回收
  • 调度器:批处理更新,优化性能

与 Vue2 的对比

特性 Vue2 Vue3
实现方式 Object.defineProperty Proxy
新增属性 需要 Vue.set 自动响应
数组变化 需要变异方法 全部响应
性能 初始化慢 初始化快
内存占用 较高 较低(WeakMap)