对Vue响应式变量的探究

前置

WeekMap

是一种特殊的map,他的键类型只能是对象。并且有以下特征

  • 弱引用:对键是弱引用,不会阻止垃圾回收

  • 不可枚举:无法获取所有键值对

  • 长度不可知

set集合

具有元素唯一性

Effect副作用

是指除了返回函数值之外,还会与函数外部状态进行交互的操作

// 1. DOM 操作
document.getElementById('app').textContent = 'Hello'

// 2. 修改外部变量
let externalVar = 0
function increment() { externalVar++ }

// 3. 控制台输出
console.log('This is a side effect')

// 4. 网络请求
fetch('/api/data')

在Vue的响应式中特指修改target的目标变量的函数

响应式代码的基础结构

function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      track(target, key) // 依赖收集
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      Reflect.set(target, key, value, receiver)
      trigger(target, key) // 触发更新
      return true
    },
    deleteProperty(target, key) {
      const result = Reflect.deleteProperty(target, key)
      trigger(target, key)
      return result
    }
  })
}

track函数

作用:在读取属性时,建立当前执行的 effect(副作用函数)被访问函数依赖关系

// 存储依赖关系的全局数据结构
const targetMap = new WeakMap() // WeakMap<target, Map<key, Set<effect>>>

let activeEffect = null // 当前正在执行的effect

function track(target, key) {
  if (!activeEffect) return // 没有活跃的effect,直接返回
  
  // 1. 从targetMap中获取target对应的depsMap
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  
  // 2. 从depsMap中获取key对应的effect Set
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  
  // 3. 将当前activeEffect添加到dep中
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect)
    // 同时effect也记录自己属于哪些dep(用于cleanup)
    activeEffect.deps.push(dep)
  }
}

简单总结以下这个函数的实现方法:在targetMap中查找到target,并且找到正确的key,将effect函数放入effect的集合中去。

trigger函数

作用:在修改target时,通知 依赖于该属性的effect 重新执行。

function trigger(target, key) {
  // 1. 获取target对应的depsMap
  const depsMap = targetMap.get(target)
  if (!depsMap) return // 没有依赖,直接返回
  
  // 2. 获取key对应的所有effect
  const effects = depsMap.get(key)
  
  // 3. 创建effectsToRun避免无限循环
  const effectsToRun = new Set()
  if (effects) {
    effects.forEach(effect => {
      // 避免当前正在执行的effect再次触发,导致无限循环
      if (effect !== activeEffect) {
        effectsToRun.add(effect)
      }
    })
  }
  
  // 4. 执行所有相关的effect
  effectsToRun.forEach(effect => {
    if (effect.options.scheduler) {
      // 如果有调度器,通过调度器执行
      effect.options.scheduler(effect)
    } else {
      // 否则直接执行
      effect()
    }
  })
}

effect系统

function effect(fn, options = {}) {
  const effectFn = () => {
    cleanup(effectFn) // 先清除旧依赖
    activeEffect = effectFn
    const res = fn() // 在这里触发原始函数,并且更新响应式数据
    activeEffect = undefined
    return res
  }
  
  effectFn.options = options
  effectFn.deps = [] // 存储所有包含此effect的dep集合
  effectFn() // 立即执行一次
  
  return effectFn
}

// 清除effect与所有dep的关联
function cleanup(effectFn) {
  effectFn.deps.forEach(dep => {
    dep.delete(effectFn)
  })
  effectFn.deps.length = 0
}
posted @ 2025-05-13 10:20  Rosyr  阅读(13)  评论(0)    收藏  举报