watch 是基于 Watcher 实例实现的,是一个 user watcher,支持不同类型 watcher 的 4 个类型 deep watcher 为了监听对象的子属性的变化,所以在 watch 中设置 deep:true 属性,实际上会 触发 traverse 函数,traverse 函数是对对象做深层遍历,遍历就会触发子对象的 getter,这样就依赖收集,订阅子对象的 watcher,…

  1. watch 是基于 Watcher 实例实现的,是一个 user watcher,支持不同类型
  2. watcher 的 4 个类型
    1. deep watcher

      为了监听对象的子属性的变化,所以在 watch 中设置 deep:true 属性,实际上会 触发 traverse 函数,traverse 函数是对对象做深层遍历,遍历就会触发子对象的 getter,这样就依赖收集,订阅子对象的 watcher,这样对 watch 中每一个值的修改,都会触发 watcher 的回调函数

    2. user watcher

      通过 this.$watch 创建一个 watcher

    3. computed watcher

      就是计算属性 computed

    4. sync watcher

      一般是把 watcher 推到一个队列中,在 nextTick 中执行 watcher 的回调函数 ,sync watcher 是在 tick 中执行 watcher 的回调函数

  3. 所以计算属性适合模板渲染,而 watch 适合监听某个值的变化,然后做一些业务逻辑

watch 源码

  1. watch 的执行过程
// src/core/instance/state.js
function initWatch(vm: Component, watch: Object) {
  // 对watch进行遍历,拿到每一个handler,如果handler是一个数组,则遍历这个数组
  for (const key in watch) {
    const handler = watch[key];
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        // 最终调用createWatcher
        createWatcher(vm, key, handler[i]);
      }
    } else {
      createWatcher(vm, key, handler);
    }
  }
}
  1. createWatcher
// src/core/instance/state.js
function createWatcher(vm: Component, expOrFn: string | Function, handler: any, options?: Object) {
  // 对handler类型判断
  if (isPlainObject(handler)) {
    options = handler;
    handler = handler.handler;
  }
  if (typeof handler === 'string') {
    handler = vm[handler];
  }
  // 最终执行vm.$watch函数,$watch是Vue原型上的方法
  return vm.$watch(expOrFn, handler, options);
}
  1. vm.$watch(expOrFn, handler, options)
// src/core/instance/state.js
Vue.prototype.$watch = function(expOrFn: string | Function, cb: any, options?: Object): Function {
  const vm: Component = this;
  // 如果cb是一个对象,则调用createWatcher
  // this.$watch是Vue暴露在外面的钩子函数
  if (isPlainObject(cb)) {
    return createWatcher(vm, expOrFn, cb, options);
  }
  options = options || {};
  options.user = true;
  // 实例化一个watcher
  // 这个watcher不是渲染watcher,而是一个 自定义watcher,所以watcher.user为true
  // 一但watcher的数据有变化,就会执行watcher的润方法,执行回调函数cb
  const watcher = new Watcher(vm, expOrFn, cb, options);
  if (options.immediate) {
    cb.call(vm, watcher.value);
  }
  // 该方法会调用teardown方法移除这个watcher
  return function unwatchFn() {
    watcher.teardown();
  };
};

Watcher options

  1. watcher 一共有 4 种类型
// src\core\observer\watcher.js
if (options) {
  this.deep = !!options.deep;
  this.user = !!options.user;
  this.computed = !!options.computed;
  this.sync = !!options.sync;
  // ...
} else {
  this.deep = this.user = this.computed = this.sync = false;
}
  1. deep watcher

如果在 data 中定义了一个有子属性 b 的 a 对象,在 watch 中监听 a 对象,做了一个 a.b=2 的操作,但是 watch 却没有监听到 a.b 的 getter,也就是说,不用 depp watcher 就只能触发 a 的 getter, 不能触发 a.b 的 getter,虽然也触发了 a.b 的 setter,但是没有可以通知的对象,所以不会触发 watch 的回调函数

var vm = new Vue({
  data() {
    a: {
      b: 1;
    }
  },
  watch: {
    a: {
      handler(newVal) {
        console.log(newVal);
      }
    }
  }
});

-->

watch: {
  a: {
    deep: true,
    handler(newVal) {
      console.log(newVal)
    }
  }
}

在 watcher 执行 get 求值的过程中,如果 deep 为 true,会调用 traverse 函数

// src\core\observer\watcher.js
get() {
  let value = this.getter.call(vm, vm)
  // ...
  if (this.deep) {
    traverse(value)
  }
}
//  src/core/observer/traverse.js
// 实际上是对一个对象做深层递归遍历,在遍历过程中就是对子对象的访问,会触发子对象的getter
// 这样就可以收集到依赖,然后订阅子对象的的watcher
// 在遍历过程中会记录子对象watcher的dep.id到seenObjects中,避免以后重复访问
// 这样对watch中的每一个值修改,也会触发watcher的回调函数
import { _Set as Set, isObject } from '../util/index';
import type { SimpleSet } from '../util/index';
import VNode from '../vdom/vnode';
const seenObjects = new Set();
export function traverse(val: any) {
  _traverse(val, seenObjects);
  seenObjects.clear();
}
function _traverse(val: any, seen: SimpleSet) {
  let i, keys;
  const isA = Array.isArray(val);
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
    return;
  }
  if (val.__ob__) {
    const depId = val.__ob__.dep.id;
    if (seen.has(depId)) {
      return;
    }
    seen.add(depId);
  }
  if (isA) {
    i = val.length;
    while (i--) _traverse(val[i], seen);
  } else {
    keys = Object.keys(val);
    i = keys.length;
    while (i--) _traverse(val[keys[i]], seen);
  }
}
  1. user watcher

通过 vm.$watch 创建的 watcher 就是一个 user watcher

// src\core\observer\watcher.js
// 对watcher求值,然后执行回调的时候,处理一下错误
get() {
  if (this.user) {
    handleError(e, vm, `getter for watcher "${this.expression}"`)
  } else {
    throw e
  }
},
getAndInvoke() {
  // ...
  if (this.user) {
    try {
      this.cb.call(this.vm, value, oldValue)
    } catch (e) {
      handleError(e, this.vm, `callback for watcher "${this.expression}"`)
    }
  } else {
    this.cb.call(this.vm, value, oldValue)
  }
}
  1. computed watcher 就是 computed 计算属性

  2. sync watcher

watch 中的数据发生变化的时候,触发 watcher.update,把这个 watcher 放到队列中,在 nextTick 才会执行这个队列中的所有 watcher 回调函数,一旦设置 sync,就可以在主线程中执行 watcher 回调函数

// src\core\observer\watcher.js
update () {
  if (this.computed) {
    // ...
  } else if (this.sync) {
    // 如果sync属性有true,那么直接执行watcher的回调函数,watcher的回调函数是一个同步的过程
    this.run()
  } else {
    queueWatcher(this)
  }
}

暂无评论