vue源码分析(十一)

151 阅读3分钟

七、侦听属性

initState函数中,如果判断有watch,那么会执行initWatch(vm, opts.watch)方法,initWatch函数中会调用createWatcher(vm, key, handler),key是watch的key,handler为传入的回调函数。createWatcher函数会先判断handler是否是一个对象,如果是一个对象说明我们写入了watch中的handler属性,并且有可能写入了deep或者immediate或sync属性,那么让handler等于传入的handler.handler,如果传入的是一个字符串,那么他会去当前的vm实例上找,这也是handler既可以直接写一个回调函数,也可以让watch触发之后的回调去执行vm上的methods,最后调用了vm.$watch(expOrFn, handler, options),expOrFn实际是监听的数据的key,handler是回调函数,options放入了之前可能定义的熟悉,$watch是实现watch的核心,他绑定在了vue.prototype上提供了直接使用的实例方法,$watch方法会首先判断传入的cb是否是一个对象,如果是一个对象,那么可能是使用者通过实例方法在调用,他会交给之前的函数createWatcher经过和刚才相同的处理,再回到$watch方法,接着定义了options.user为true,然后通过const watcher = new Watcher(vm, expOrFn, cb, options)去new一个user watcher,然后判断他是否写入了属性immediate,如果是立即触发的user watcher那么他会执行cb.call(vm, watcher.value),最终返回unwatchFn函数。也就是说如果通过this.$watch方法创建的user watch最终会返回一个卸载函数。

// src/core/instance/state.js
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)
}
...
Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }

在创建user watcher的时候会执行new Watcher(vm, expOrFn, cb, options)expOrFn为监听的属性key,cb为监听到变化之后触发的回调函数,options为传入的配置,其中user 为true。deep,sync根据options传入的deep进行赋值,接着他会判断expOrFn的类型是否是函数,此时传入的expOrFn为一个字符串所以走下边的逻辑,首先他会执行this.getter = parsePath(expOrFn)parsePath函数首选通过split('.')对传入的key进行解析,最终返回一个函数,这个函数根据传入的obj,再通过之前的解析的路径进行寻找,如果找到那么进行返回,如果没有找到那么返回空,那么this.getter等于返回的这个函数,最后会去执行this.get()。进行get函数之后,通过调用pushTarget,让当前dep的target指向当前的user watcher,然后通过 value = this.getter.call(vm, vm)进行之前定义好getter函数的执行,传入的参数是当前的vm实例,这样通过getter函数的调用访问到当前监听的数据,被监听的数据触发依赖收集,此时的dep.target就是当前的user watcher所以形成了依赖关系,当监听的数据发生了更新,则会触发user watcher的update函数中的queueWatcher,也就是flushSchedulerQueueflushSchedulerQueue函数当中会触发watcher.run()run函数首先会执行user wathcer 的get函数,和之前分析的一样,他会绑定依赖关系等等步骤,最终get函数会return出拿到的value,也就是数据更新后的值,之后他会拿新的value和旧的value进行比对,如果不相等,或value是个对象,或者设置了deep属性,当前的user为true,所以会执行 this.cb.call(this.vm, value, oldValue);也就是之前传入的回调函数。

export default class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object
  ){
    this.vm = vm
    ...
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      ...
      this.sync = !!options.sync
      ...
    } else {
     ...
    }
   ...
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

最后如果你传入的属性是一个deep:true,那么在执行watcher get 函数的时候,会通过traverse进行一个处理,traverse函数是对当前的value进行了深度的遍历,也就是让当前的user watcher订阅了其中的每一个属性的更新。如果你传入的属性是一个immediate:true,那么当$watch函数中new watcher之后会执行一次回调函数cb.call(vm, watcher.value)。最后如果传入的sync是true,那么他会优先执行当前user watcher的run方法,也就是说,会优先触发监听,这个地方是当你两个user watcher同时触发,由于在创建的过程中定义的id,在之后的watcher的sort中,先创建的会被先执行,如果希望后创建的先执行回调,那么可以通过传入sync属性,来调整回调函数触发的优先级。

 get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
     ...
    } catch (e) {
     ...
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      ...
    }
    return value
  }
  
  ...
  
  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    ...
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
      ...
      }
    }
    ...
  }
  
  ...
  
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
     ...
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }