阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 依赖收集与追踪的详细流程

依赖收集与追踪的详细流程

作者:陈川 阅读数:49771人阅读 分类: Vue.js

依赖收集与追踪的流程概述

Vue3的响应式系统核心在于依赖收集与追踪。当数据变化时,能自动通知依赖它的副作用重新执行。这个过程通过Proxy拦截get/set操作,结合tracktrigger函数实现。

响应式对象的创建

通过reactive()函数创建响应式对象时,内部会调用createReactiveObject

function reactive(target) {
  return createReactiveObject(
    target,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

function createReactiveObject(target, baseHandlers) {
  const proxy = new Proxy(target, baseHandlers)
  return proxy
}

baseHandlers包含Proxy的get/set陷阱:

const mutableHandlers = {
  get: createGetter(),
  set: createSetter()
}

getter中的依赖收集

当访问响应式属性时,触发get陷阱:

function createGetter() {
  return function get(target, key, receiver) {
    const res = Reflect.get(target, key, receiver)
    track(target, key) // 关键依赖收集
    if (isObject(res)) {
      return reactive(res) // 深层响应式
    }
    return res
  }
}

track函数建立属性与当前运行副作用的关系:

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) // 将当前effect添加到依赖集合
  activeEffect.deps.push(dep) // 反向记录
}

副作用的注册与执行

副作用通过effect函数注册:

let activeEffect

function effect(fn) {
  const effectFn = () => {
    cleanup(effectFn) // 清除旧依赖
    activeEffect = effectFn
    fn()
  }
  effectFn.deps = []
  effectFn()
}

清理旧依赖避免无效更新:

function cleanup(effectFn) {
  for (const dep of effectFn.deps) {
    dep.delete(effectFn)
  }
  effectFn.deps.length = 0
}

setter中的依赖触发

修改属性值时触发set陷阱:

function createSetter() {
  return function set(target, key, value, receiver) {
    const oldValue = target[key]
    const result = Reflect.set(target, key, value, receiver)
    if (hasChanged(value, oldValue)) {
      trigger(target, key) // 触发更新
    }
    return result
  }
}

trigger查找并执行相关副作用:

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const effects = depsMap.get(key)
  const effectsToRun = new Set(effects)
  effectsToRun.forEach(effect => effect())
}

嵌套effect的处理

组件渲染可能产生嵌套effect:

effect(() => {
  console.log('外层effect')
  effect(() => {
    console.log('内层effect')
    temp2 = obj.bar // 收集内层依赖
  })
  temp1 = obj.foo // 收集外层依赖
})

通过effectStack维护执行上下文:

const effectStack = []

function effect(fn) {
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn
    effectStack.push(effectFn)
    fn()
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
  }
  effectFn.deps = []
  effectFn()
}

数组方法的特殊处理

数组的push/pop等方法需要额外处理:

const arrayInstrumentations = {
  push() {
    track(this, 'length') // 追踪length变化
    return Array.prototype.push.apply(this, arguments)
  }
}

function createGetter() {
  return function get(target, key, receiver) {
    if (isArray(target) && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    // ...原有逻辑
  }
}

调度执行控制

通过scheduler控制触发时机:

function trigger(target, key) {
  // ...
  const run = (effect) => {
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }
  effectsToRun.forEach(run)
}

示例使用:

effect(() => {
  console.log(obj.foo)
}, {
  scheduler(effect) {
    setTimeout(effect, 1000) // 延迟执行
  }
})

计算属性的实现

计算属性基于effect的懒执行:

function computed(getter) {
  let value
  let dirty = true
  
  const effectFn = effect(getter, {
    lazy: true,
    scheduler() {
      dirty = true
      trigger(obj, 'value') // 手动触发依赖
    }
  })
  
  const obj = {
    get value() {
      if (dirty) {
        value = effectFn()
        dirty = false
      }
      track(obj, 'value') // 手动收集依赖
      return value
    }
  }
  
  return obj
}

watch的实现原理

watch基于effect和调度器:

function watch(source, cb) {
  let getter
  if (isFunction(source)) {
    getter = source
  } else {
    getter = () => traverse(source)
  }
  
  let oldValue
  const effectFn = effect(
    () => getter(),
    {
      scheduler() {
        const newValue = effectFn()
        cb(newValue, oldValue)
        oldValue = newValue
      }
    }
  )
  
  oldValue = effectFn()
}

递归读取属性确保全部追踪:

function traverse(value, seen = new Set()) {
  if (!isObject(value) || seen.has(value)) return
  seen.add(value)
  for (const k in value) {
    traverse(value[k], seen)
  }
  return value
}

响应式系统的性能优化

  1. 依赖收集层级优化:
function track(target, key) {
  if (!shouldTrack) return // 全局开关
  if (key === '__proto__' || key === 'constructor') return
  // ...其余逻辑
}
  1. 避免重复trigger:
function set(target, key, value) {
  if (Array.isArray(target) && key === 'length') {
    if (value >= target.length) return // 忽略长度增大
  }
  // ...原有逻辑
}
  1. 批量更新机制:
let isFlushing = false
const queue = new Set()

function queueJob(job) {
  queue.add(job)
  if (!isFlushing) {
    isFlushing = true
    Promise.resolve().then(() => {
      try {
        queue.forEach(job => job())
      } finally {
        isFlushing = false
        queue.clear()
      }
    })
  }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌