vue2/3 watch原理

watch

Vue2 watch 的工作流程

vue2 4.初始化数据流程中,new了一些Watcher(),watch就是这些watcher

  1. 初始化 :为每个 watch 项创建 Watcher 实例
  2. 依赖收集Watcher 首次执行 get(),触发响应式数据的 getter,将自身收集为依赖
  3. 深度监听 :如果设置了 deep: true,会递归遍历对象的所有属性,触发它们的 getter,从而收集所有嵌套属性的依赖
  4. 触发更新 :当响应式数据变化时,触发 setter,调用所有依赖的 update() 方法
  5. 执行回调Watcherupdate() 比较新旧值,如果变化则执行回调

在vue3中,watch,是对ReactiveEffect的另一种effect实现

Vue3 watch 的工作流程

vue3 四、创建副作用中,通过ReactiveEffect创建了effect,watch是对effect的更深度的实现

  1. 标准化源 :将各种形式的 source(ref、reactive、getter 函数、数组)统一为 getter 函数

  2. 深度监听处理 :如果启用深度监听,会递归遍历对象,触发所有属性的 getter,收集依赖

  3. 创建 effect :创建 ReactiveEffect,将 getter 作为副作用函数

  4. 依赖收集 :首次执行 effect.run(),触发 getter,读取响应式数据,完成依赖收集

  5. 调度更新 :当依赖的数据变化时,根据 flush 选项决定何时执行回调

    • 'pre':组件更新前执行(默认)
    • 'post':组件更新后执行(类似 Vue2 的 $nextTick
    • 'sync':同步执行
  6. 执行回调 :执行用户提供的回调函数,支持副作用清理

js 复制代码
// 简化版 watch 实现
function watch(source, cb, options = {}) {
  const {
    immediate = false,
    deep = false,
    flush = 'pre' // 'pre' | 'post' | 'sync'
  } = options;
  
  let getter;
  let cleanup;
  let oldValue;
  
  // 1. 标准化 source 为 getter 函数
  if (isRef(source)) {
    // ref 类型
    getter = () => source.value;
  } else if (isReactive(source)) {
    // reactive 对象
    getter = () => source;
    // reactive 对象默认深度监听
    deep = true;
  } else if (typeof source === 'function') {
    // getter 函数
    getter = source;
  } else if (Array.isArray(source)) {
    // 数组形式,监听多个源
    getter = () => source.map(s => {
      if (isRef(s)) return s.value;
      if (isReactive(s)) return traverse(s);
      if (typeof s === 'function') return s();
      return s;
    });
  } else {
    getter = () => {};
  }
  
  // 2. 深度监听处理
  if (deep) {
    const baseGetter = getter;
    getter = () => traverse(baseGetter());
  }
  
  // 3. 定义作业函数
  const job = () => {
    if (!effect.active) return;
    
    const newValue = effect.run();
    
    // 清理副作用
    if (cleanup) {
      cleanup();
    }
    
    // 执行回调,传入清理函数
    cb(newValue, oldValue, (fn) => {
      cleanup = () => {
        fn();
        cleanup = null;
      };
    });
    
    oldValue = newValue;
  };
  
  // 4. 创建调度器
  let scheduler;
  if (flush === 'sync') {
    scheduler = job;
  } else if (flush === 'post') {
    scheduler = () => queuePostFlushCb(job);
  } else { // 'pre'
    scheduler = () => queueJob(job);
  }
  
  // 5. 创建响应式 effect
  const effect = new ReactiveEffect(getter, scheduler);
  
  // 6. 初始化
  if (immediate) {
    job();
  } else {
    oldValue = effect.run();
  }
  
  // 7. 返回停止函数
  return () => {
    effect.stop();
    if (cleanup) cleanup();
  };
}

// 深度遍历(与 Vue2 类似,但使用 Proxy)
function traverse(value, seen = new Set()) {
  if (!isObject(value) || value === null || seen.has(value)) {
    return value;
  }
  
  seen.add(value);
  
  if (isRef(value)) {
    traverse(value.value, seen);
  } else if (Array.isArray(value)) {
    for (let i = 0; i < value.length; i++) {
      traverse(value[i], seen);
    }
  } else if (value instanceof Map) {
    value.forEach((val, key) => {
      traverse(val, seen);
    });
  } else if (value instanceof Set) {
    value.forEach(val => {
      traverse(val, seen);
    });
  } else {
    for (const key in value) {
      traverse(value[key], seen);
    }
  }
  
  return value;
}

watchEffect 的实现

Vue3 还提供了 watchEffect,自动追踪其同步执行期间访问的所有响应式依赖:

js 复制代码
function watchEffect(effect, options = {}) {
  return doWatch(effect, null, options);
}

function doWatch(source, cb, options = {}) {
  const { flush = 'pre' } = options;
  
  let getter;
  if (typeof source === 'function') {
    getter = source;
  }
  
  // 响应式 effect
  const runner = effect(getter, {
    lazy: true,
    scheduler: () => {
      if (cb) {
        // watch
        scheduler();
      } else {
        // watchEffect:直接重新运行 effect
        runner();
      }
    }
  });
  
  // 记录初始值(仅 watch 需要)
  let oldValue;
  if (cb) {
    oldValue = runner();
  } else {
    // watchEffect 立即执行
    runner();
  }
  
  return () => {
    runner.stop();
  };
}
特性 watch watchEffect
监听方式 显式声明监听的数据源 自动追踪回调函数中使用的响应式依赖
回调参数 接收(newValue, oldValue, onCleanup) 只接收onCleanup清理函数
初始执行 默认不立即执行,需设置immediate: true 立即执行一次
返回值 返回新值和旧值 没有返回值,只能访问当前值
依赖收集 基于传入的数据源 基于回调函数执行时访问的响应式数据
适用场景 需要新旧值对比、精确控制监听目标 逻辑组合、响应式副作用聚合

总结

Vue的watch是通过依赖收集建立监听关系,在响应式数据变化时自动触发回调执行的侦听机制。

相关推荐
代码搬运媛7 小时前
Jest 测试框架详解与实现指南
前端
counterxing8 小时前
我把 Codex 里的 Skills 做成了一个 MCP,还支持分享
前端·agent·ai编程
wangqiaowq8 小时前
windows下nginx的安装
linux·服务器·前端
之歆9 小时前
DAY_12JavaScript DOM 完全指南(二):实战与性能篇
开发语言·前端·javascript·ecmascript
发现一只大呆瓜9 小时前
Vite凭什么这么快?3分钟带你彻底搞懂 Vite 热更新的幕后黑手
前端·面试·vite
Maimai108089 小时前
React如何用 @microsoft/fetch-event-source 落地 SSE:比原生 EventSource 更灵活的实时推送方案
前端·javascript·react.js·microsoft·前端框架·reactjs·webassembly
kyriewen11 小时前
产品经理把PRD写成“天书”,我用AI半小时重写了一遍,他当场愣住
前端·ai编程·cursor
humcomm11 小时前
元框架的工作原理详解
前端·前端框架
canonical_entropy11 小时前
Attractor Before Harness: AI 大规模开发的方法论
前端·aigc·ai编程
zhangxingchao12 小时前
多 Agent 架构到底怎么选?从 Claude Agent Teams、Cognition/Devin 到工程落地原则
前端·人工智能·后端