理解vue2中的watch监听

  1. vue实例组件初始化过程中,在执行initState(vm)方法初始化状态时,判断options.watch有值时会进行initWatch(vm, options.watch)处理,然后对watch对象中的每个watch属性执行createWatcher方法
javascript 复制代码
function initState(vm) {
  // 传入的watch
  if (options.watch) { 
    initWatch(vm, options.watch)
  }
}
function initWatch(vm, watch) {
  for (let key in watch) {
    let handler = watch[key]
    // 这里不写数组的情况了,平时没用过
    createWatcher(vm, key, handler)
  }
}
  1. 执行createWatcher方法,拿到watch属性的具体回调函数,再执行vm.$watch方法
javascript 复制代码
function createWatcher(vm, expOrFn, handler, options) {
  // 对象
  if (handler.toString() === '[object Object]') {
    options = handler
    handler = handler.handler
  }
  // 监听的方法是methods里的方法
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}
  1. 执行$watch方法,给属性创建一个对应的watcher观察者实例,用来依赖收集。如果设置了immediate:true标识,表示立即执行一次回调函数
javascript 复制代码
Vue.prototype.$watch = function (expOrFn, cb, options) {
  options = options || {}
  // 用户自己设置的watch
  options.user = true 
  let watcher = new Watcher(this, expOrFn, cb, options)
  // 立即执行
  if (options.immediate) {
    pushTarget();
    cb.apply(this, [watcher.value])
    popTarget();
  }
}
  1. 创建watcher实例,进行依赖收集。如果watch的属性是定义在data中的,此watcher实例会被push进data中对应的属性的Dep对象的subs数组中。如果watch的属性是计算属性,此watcher实例会被push进计算属性中所有响应式数据的Dep对象的subs数组中
javascript 复制代码
class Watcher {
  constructor(vm, expOrFn, cb, options) {
    //...
    if (options) {
      this.user = !!options.user;
      this.deep = !!options.deep; // 是否深度监听
      this.lazy = !!options.lazy;
    }
    this.vm = vm;
    this.cb = cb; // 回调函数
    this.active = true;
    // 获取watch属性在vm实例上对应的值
    this.getter = parsePath(expOrFn); 
    this.value = this.lazy ? undefined : this.get();
  }
  get() {
    pushTarget(this);
    let vm = this.vm;
    let value = this.getter.call(vm, vm);
    // 深度监听
    if (this.deep) {
      traverse(value);
    }
    popTarget();
    return value;
  }
  update() {
  	// 进入异步队列,这个之前讲过,会触发this.run()
    queueWatcher(this);
  }
  run() {
    let value = this.get();
    if (value !== this.value || isObject(value) || this.deep) {
      let oldValue = this.value;
      this.value = value;
      if (this.user) {
        // 执行回调函数
        this.cb.apply(this.vm, [value, oldValue]);
      }
    }
  }
}

// 获取watch属性在vm实例上对应的值
// 可以是data中定义的属性,也可以是计算属性
function parsePath(path) {
  let segments = path.split(".");
  return function (obj) {
    if (!obj) return;
    for (let i = 0; i < segments.length; i++) {
      obj = obj[segments[i]];
    }
    return obj;
  };
}
function isObject(obj) {
  return obj !== null && typeof obj === "object";
}

// 递归处理触发每一个属性的getter,形成深度依赖收集
let seenObjects = new Set();
function traverse(val) {
  _traverse(val, seenObjects);
  seenObjects.clear(); // 清空
  return val;
}
function _traverse(val, seen) {
  let i;
  let keys;
  if (val.__ob__) {
    var depId = val.__ob__.dep.id;
    // 处理循环引用的情况
    if (seen.has(depId)) {
      return;
    }
    seen.add(depId);
  }
  if (Array.isArray(val)) {
    i = val.length;
    while (i--) _traverse(val[i], seen);
  } else {
    keys = Object.keys(val);
    i = keys.length;
    // 触发响应式数据的getter
    while (i--) _traverse(val[keys[i]], seen);
  }
}
  1. 当监听的响应式数据发生变化时,触发watcher.update()方法,最终执行watcher.run()方法,执行回调函数。

总结:watch与computed的区别

  1. watch无缓存功能,所监听的响应式属性值发生变化时,都会立即触发回调函数;
  2. watch更灵活,可以监听任何响应式数据或表达式的变更;
  3. watch支持深度监听,可支持在回调函数中执行异步操作,如网络请求等等。适用场景:数据变更后执行自定义逻辑,如网络请求、DOM操作。需要监听深层对象结构变化时。
  4. computed依赖其他数据属性进行计算,并返回计算结果,必须return出去一个值;
  5. computed具有缓存功能,计算结果进行缓存,提高性能。适用场景:所需的值是要根据多个响应式属性计算得出,并且会被频繁使用的值;
相关推荐
掘了38 分钟前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅41 分钟前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
吹牛不交税2 小时前
admin.net-v2 框架使用笔记-netcore8.0/10.0版
vue.js·.netcore
Cobyte2 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc