三类watcher

269 阅读2分钟

watcher 是什么

  • 当属性变化后会通知自己对应的 watcher 去更新(派发更新)
  • 在update方法中,不同类型会执行不同逻辑。
  • 分为三种,渲染watcher、计算watcher、侦听watcher

代码如下

export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  dep: Dep;
  deps: Array<Dep>;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    
    this.cb = cb
    this.id = ++uid
    this.deps = []
    this.newDeps = []
  }

  get () {
    //Dep.target = this
    pushTarget(this)

    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      ...
    } finally {
      popTarget()
      this.cleanupDeps()
    }
    return value
  }
  
  // watcher添加到dep
  addDep (dep: Dep) {
     dep.addSub(this)
     this.newDeps.push(dep)
  }

  cleanupDeps () {
     dep.removeSub(this)
     this.newDeps = this.deps
  }
  
  // 派发更新
  update () {
    if (this.computed) {
      if (this.dep.subs.length === 0) {
        this.dirty = true
      } else {
        this.getAndInvoke(() => {
          this.dep.notify()
        })
      }
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }  
  
  
  // queueWatcher维护watcher队列,依次执 watcher.run()
  run () {
    if (this.active) {
      this.getAndInvoke(this.cb)
    }
  }

  getAndInvoke (cb: Function) {
    const value = this.get()
    cb.call(this.vm, value, oldValue)
  }

}

渲染watcher

  • 最终会 导致页面视图更新

queueWatcher 会执行queue.push(this) 把当前watcher推入队列,最终执行watcher.run()

getAndInvoke 对于渲染 watcher ,this.cb如下:

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}

computed watcher

  • 计算属性,返回一个值
  • 依赖的属性变化会导致computed重新计算,使用该值(触发该watcher的get方法)才计算
 computed: {
    reverseMessage: function(value){
      console.log(" I'm reverseMessage" )
      return this.message.split('').reverse().join('');
    }
  }
  • 为computed对象上每个key添加一个watcher
  • 该watcher会持有一个dep属性,dep的subs为依赖该属性的其他watcher,记为 watcher_sub
  • get计算属性会触发computedGetter方法
  • watcher.depend 会将Dep.target(watcher_sub)添加进this.dep.subs里
  • watcher.evaluate会触发 this.dep.subs.notify()即watcher_sub的update方法 源码如下:
 const computedWatcherOptions = { computed: true }
function initComputed (vm: Component, computed: Object) {
  const watchers = vm._computedWatchers = Object.create(null)

  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get

    watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
    )

    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    }
  }
}


export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
    sharedPropertyDefinition.get = createComputedGetter(key)
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      watcher.depend()
      return watcher.evaluate()
    }
  }
}

watch watcher

  • 侦听属性,它主要用于侦听数据的变化,在数据发生变化的时候执行一些操作
  • 依赖的属性变化会执行watch的回调
 watch: {
    counter: function(newValue, oldValue){
      if(this.counter == 10){
        this.counter = 0;
      }
    }
  }
  • 为watch对象上每个key添加一个watcher
  • new Watcher会执行 this.value=this.get()会获取监测的属性counter,进而将该watcher添加进counter的dep.subs;当改变counter时会执行dep.notify(),进而执行 new Watcher时的cb,达到监测counter变化执行该回调
  • 全局方法.$watch(),返回一个销毁watcher的方法
if (opts.watch && opts.watch !== nativeWatch) {
  initWatch(vm, opts.watch)
}

function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
      const handler = watch[key]
      createWatcher(vm, key, handler)
  }
}

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  ...
  return vm.$watch(expOrFn, handler, options)
}

Vue.prototype.$watch = function (
  expOrFn: string | Function,
  cb: any,
  options?: Object
): Function {
  const vm: Component = this
  options.user = true
  const watcher = new Watcher(vm, expOrFn, cb, options)
  if (options.immediate) {
    cb.call(vm, watcher.value)
  }
  return function unwatchFn () {
    watcher.teardown()
  }
}

export default class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function,
  ) {
     this.getter = expOrFn
     this.value = this.get()
  }

  get () {
    //Dep.target = this
    pushTarget(this)

    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      ...
    } finally {
      popTarget()
      this.cleanupDeps()
    }
    return value
  }
  ...
}

欢迎关注我的前端自检清单,我和你一起成长