【Vue源码】18.响应式原理-侦听器

响应式原理-侦听器

侦听器的初始化也是定义在 initState 函数中,定义在 src/core/instance/state.js 中:

function initWatch(vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key];
    // 支持 watch 的同一个 key 对应多个 handler
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i]);
      }
    } else {
      createWatcher(vm, key, handler);
    }
  }
}
复制代码

initWatch 主要对 watcher 对象做遍历,拿到每一个 handler 调用 createWatcher

function createWatcher(
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler;
    handler = handler.handler;
  }
  if (typeof handler === "string") {
    handler = vm[handler];
  }
  return vm.$watch(expOrFn, handler, options);
}
复制代码

createWatcher 首先对 handler 做类型判断,然后调用 vm.$watch$watchVue 原型上的方法,它是在执行 stateMixin 的时候定义的:

Vue.prototype.$watch = function (
  expOrFn: string | Function,
  cb: any,
  options?: Object
): Function {
  const vm: Component = this;
  // $watch 方法是用户可以直接调用的,它可以传递一个对象,也可以传递函数
  if (isPlainObject(cb)) {
    return createWatcher(vm, expOrFn, cb, options);
  }
  options = options || {};
  options.user = true;
  // 这是一个 user watcher
  const watcher = new Watcher(vm, expOrFn, cb, options);
  if (options.immediate) {
    cb.call(vm, watcher.value);
  }
  return function unwatchFn() {
    watcher.teardown();
  };
};
复制代码

也就是说,侦听器 watch 最终会调用 $watch 方法,这个方法首先判断 cb 如果是一个对象,则调用 createWatcher 方法,这是因为 $watch 方法是用户可以直接调用的,它可以传递一个对象,也可以传递函数。接着执行 const watcher = new Watcher(vm, expOrFn, cb, options) 实例化了一个 watcher,这里需要注意一点这是一个 user watcher。通过实例化 watcher 的方式,一旦我们 watch 的数据发送变化,它最终会执行 watcherrun 方法,执行回调函数 cb,并且如果我们设置了 immediatetrue,则直接会执行回调函数 cb

所以本质上侦听器也是基于 Watcher 实现的,它是一个 user watcher。其实 Watcher 支持了不同的类型,下面我们梳理一下它有哪些类型以及它们的作用。

deep watcher

watcher 执行 get 求值的过程中有一段逻辑:

get() {
  let value = this.getter.call(vm, vm)
  // ...
  if (this.deep) {
    traverse(value)
  }
}
复制代码

如果为 deep watcher 则执行 traverse(value)traverse 定义在 src/core/observer/traverse.js 中:

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);
  }
}
复制代码

它的本质就是对一个对象做深层递归遍历,因为遍历过程中就是对一个子对象的访问,会触发它们的 getter 过程,这样就可以收集到依赖,也就是订阅它们变化的 watcher,这个函数实现还有一个小的优化,遍历过程中会把子响应式对象通过它们的 dep id 记录到 seenObjects,避免以后重复访问。

那么在执行了 traverse 后,我们再对 watch 的对象内部任何一个值做修改,也会调用 watcher 的回调函数了。

user watcher

在对 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)
  }
}
复制代码

computed watcher

computed watcher17.响应式原理-计算属性量身定制的。

sync watcher

当响应式数据发送变化后,触发了 watcher.update(),只是把这个 watcher 推送到一个队列中,在 nextTick 后才会真正执行 watcher 的回调函数。而一旦我们设置了 sync,就可以在当前 Tick 中同步执行 watcher 的回调函数。

update () {
  if (this.computed) {
    // ...
  } else if (this.sync) {
    this.run()
  } else {
    queueWatcher(this)
  }
}
复制代码

总结

18.响应式原理-侦听器.png
通过本节分析,侦听器本质上是 user watcher,适用于观测某个值的变化去完成一段复杂的业务逻辑。同时我们又了解了 watcher4options,通常我们会在创建 user watcher 的时候配置 deepsync,可以根据不同的场景做相应的配置。

源码分析 GitHub 地址

参考:Vue.js 技术揭秘

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享