vue 数据更新到视图变化全过程

Vue 数据更新到视图更新的全过程

Vue 2.x 响应式原理

1. 数据劫持阶段

javascript 复制代码
// Vue 2 使用 Object.defineProperty 劫持数据
function defineReactive(obj, key, val) {
  const dep = new Dep() // 依赖收集器
  
  Object.defineProperty(obj, key, {
    get() {
      // 依赖收集:将当前 Watcher 添加到 dep
      if (Dep.target) {
        dep.depend()
      }
      return val
    },
    set(newVal) {
      if (newVal === val) return
      val = newVal
      // 触发更新:通知所有依赖该数据的 Watcher
      dep.notify()
    }
  })
}

2. 依赖收集阶段

javascript 复制代码
// 组件渲染时收集依赖
class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm
    this.cb = cb
    this.deps = []
    this.getter = expOrFn // 渲染函数
    this.value = this.get()
  }
  
  get() {
    Dep.target = this // 设置当前 Watcher
    const value = this.getter.call(this.vm) // 执行渲染函数,触发 getter
    Dep.target = null // 清空
    return value
  }
}

3. 派发更新阶段

javascript 复制代码
class Dep {
  notify() {
    this.subs.forEach(watcher => {
      watcher.update() // 通知所有 Watcher 更新
    })
  }
}

// Watcher 更新
update() {
  queueWatcher(this) // 加入异步更新队列
}

4. 异步更新队列

javascript 复制代码
function queueWatcher(watcher) {
  if (!flushing) {
    queue.push(watcher)
  }
  
  if (!waiting) {
    waiting = true
    nextTick(flushSchedulerQueue) // 下一个事件循环执行
  }
}

function flushSchedulerQueue() {
  // 按 id 排序,确保父组件先于子组件更新
  queue.sort((a, b) => a.id - b.id)
  
  for (let i = 0; i < queue.length; i++) {
    const watcher = queue[i]
    watcher.run() // 执行实际更新
  }
}

Vue 3.x 响应式原理

1. Proxy 劫持

javascript 复制代码
// Vue 3 使用 Proxy 劫持整个对象
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) {
      const result = Reflect.set(target, key, value, receiver)
      trigger(target, key) // 触发更新
      return result
    }
  })
}

2. 依赖收集与触发

javascript 复制代码
// 全局依赖映射
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
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  
  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => {
      scheduler ? scheduler() : effect() // 执行更新
    })
  }
}

完整更新流程

1. 用户操作触发数据变更

javascript 复制代码
// 用户点击按钮
this.count++ // 触发 setter

2. 响应式系统检测变化

  • Vue 2: Object.defineProperty 的 setter 被调用
  • Vue 3: Proxy 的 set 拦截器被调用

3. 依赖通知

javascript 复制代码
// 通知所有依赖该数据的组件
dep.notify() // Vue 2
trigger(target, key) // Vue 3

4. 调度更新

javascript 复制代码
// 将更新任务加入队列
queueWatcher(watcher) // Vue 2
queueJob(job) // Vue 3

5. 下一帧执行更新

javascript 复制代码
// 使用 nextTick 确保批量更新
nextTick(() => {
  flushQueue() // 执行所有待更新任务
})

6. 虚拟 DOM Diff

javascript 复制代码
// 生成新的虚拟 DOM
const newVNode = render()
// 与旧虚拟 DOM 对比
const patches = diff(oldVNode, newVNode)

7. 更新真实 DOM

javascript 复制代码
// 将变更应用到真实 DOM
patch(patches)

关键优化机制

1. 异步更新队列

  • 同一个事件循环内的多次数据变更会被合并
  • 避免频繁的 DOM 操作

2. 组件级别的依赖收集

  • 只有使用了响应式数据的组件才会更新
  • 粒度控制在组件级别

3. Virtual DOM Diff 算法

  • 只更新发生变化的 DOM 节点
  • 最小化 DOM 操作成本

时间线示例

markdown 复制代码
1. 用户点击 (0ms)
2. 数据变更 setter 触发 (0.1ms)
3. 依赖通知 Watcher (0.2ms)
4. 加入更新队列 (0.3ms)
5. nextTick 调度 (0.4ms)
6. 执行虚拟 DOM diff (4ms)
7. 更新真实 DOM (8ms)
8. 浏览器重绘 (16ms)

这个过程确保了数据变更能够高效、批量地反映到视图上,同时避免了不必要的性能开销。

相关推荐
IT_陈寒19 分钟前
Redis的KEYS命令把我搞崩溃了,改用SCAN才活过来
前端·人工智能·后端
Jackson__1 小时前
为什么 Agent 越聊越慢?聊聊 Context(上下文)管理
前端·agent·ai编程
KaMeidebaby1 小时前
卡梅德生物技术快报|抗体合成:多肽抗体合成工程化方案:Nsp2 保守肽多抗制备与多维度验证
前端·网络·数据库·人工智能·算法
青禾网络1 小时前
前端做音画匹配这件事,我从"随机塞"到"AI 自动对齐"
前端·github
2601_963771372 小时前
Hardening Enterprise WordPress Sites Against REST API Leaks and Bad Headers
前端·windows·word·php
蓝银草同学2 小时前
Stream 实战:博客列表排序、过滤与分页(AI 辅助学习 Java 8)
java·前端·后端
2601_957190902 小时前
飞行影院安装施工指南:场地、动感系统与影片内容配套
大数据·前端·人工智能
爱折腾的小黑牛2 小时前
简记往来批量录入功能的实现:从文本到结构化数据
前端·算法
YHHLAI3 小时前
Agent 智能体开发实战 · 第六课:MCP 协议 —— 让 Agent 跨进程调用工具
前端·人工智能
SmartBoyW3 小时前
前端死磕:一文彻底搞懂 JS 事件循环 (Event Loop) 与宏微任务
前端·javascript