理解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具有缓存功能,计算结果进行缓存,提高性能。适用场景:所需的值是要根据多个响应式属性计算得出,并且会被频繁使用的值;
相关推荐
边境悍匪几秒前
蜗牛学苑 Java 智能体学习 Day45|Knife4j+SpringBoot3、书城项目整合 MyBatis 思维导图复盘
java·开发语言·vue.js·学习·spring
南雨北斗12 分钟前
vue3项目中的env.d.ts文件
前端
烈风逍遥20 分钟前
第五篇:通用 LLM 流式对话:前后端联接的完整实现
前端·后端·架构
aixingpan21 分钟前
aixingpan.cn API开发文档:api_docs_trichart_natal_progression_sec_transit2接口指南
前端·php
江华森32 分钟前
Web 安全实战:验证机制、会话管理、SQL 注入、XSS 与 CSRF
前端
江华森38 分钟前
HTTPS 实战:TLS 握手成本、自制 CA 与证书、三大常见故障
前端
江华森41 分钟前
HTTP 再邂逅:报文结构、请求方法、状态码与 Cookie/Session
前端
szephyr1 小时前
前端错误监控实战:用 Sentry 把线上报错从“用户反馈“变成“主动发现“
前端·sentry·稳定性·前端监控·错误监控
拖孩1 小时前
给小程序加「一键发公众号贴图」,我把 wx.shareToOfficialAccount 的三个坑趟了一遍
前端·后端·微信小程序