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是通过依赖收集建立监听关系,在响应式数据变化时自动触发回调执行的侦听机制。

相关推荐
老前端的功夫2 小时前
TypeScript索引访问类型深度解析:类型系统的动态访问与模式匹配
前端·javascript·ubuntu·架构·typescript·前端框架
不务正业的前端学徒2 小时前
vue2/3响应式原理
前端
不务正业的前端学徒2 小时前
vue2/3computed原理
前端
前端付豪2 小时前
NodeJs 做了什么 Fundamentals Internals
前端·开源·node.js
爱分享的鱼鱼2 小时前
Pinia 数据跨组件共享机制与生命周期详解
前端
张元清2 小时前
大白话讲 React2Shell 漏洞:智能家居的语音助手危机
前端·javascript·react.js
wuhen_n2 小时前
手写符合A+规范的Promise
前端
小明记账簿_微信小程序2 小时前
一篇文章教会你接入Deepseek API
前端
若凡SEO2 小时前
深圳优势产业(电子 / 机械)出海独立站运营白皮书
大数据·前端·搜索引擎