vue源码分析(八)

170 阅读2分钟

二、依赖收集

在调用defineReactive函数的时候,会通过Object.defineProperty绑定getter和setter,绑定getter,触发getter的过程就是依赖收集。首先会调用 const value = getter ? getter.call(obj) : val来计算出他的value,在getter的最后会把value返回。依赖收集的过程是从判断Dep.target开始

// src/core/observer/index.js
/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  ...
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
     ...
    }
  })
}

Dep是一个类,他的目的是把数据和watcher之间联系起来,是一个中间的纽带,Dep.target在flow中的定义是一个Watcher,如果有watcher的话,会执行dep.depend(),相当于执行了Dep.target.addDep(this)也就是watcher.addDep(this)

// src/core/observer/dep.js
/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }
  ...
   depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
}

那么渲染watcher会在什么时候执行呢(什么时机调用Watcher类)。在vue初始化的过程中,会先执行mountcompoennt方法,调用updateComponent = ()=> vm._update(vm._render(), hydrating),updateComponent实际上会作为new Watcher的第二个参数传入

// src/core/instance/lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  ...
  let updateComponent
  ...
  updateComponent = () => {
    vm._update(vm._render(), hydrating)
  }
}

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  ...
}

当执行new Watcher的时候,constructor会执行vm._watcher=this,然后会让当前的getter = expOrFn,expOrFn就是在创建渲染watcher传入的vm._update(vm._render(), hydrating),最后调用this.get()。执行get方法,首先会调用pushTarget方法,pushTarget会执行Dep.target = target,也就是让Dep的target等于了当前的渲染watcher。还会往targetStack数组中,把当前的wather实例push进去,与此对应的还有popTarget方法,他给当前targetStack做一个pop操作,删除最后一项,同时让Dep.target = targetStack[targetStack.length - 1],这样做的目的是当出现了组件嵌套的情况,可以把子的组件pop,还原父的组件watcher。也就是说执行pushTarget会把当前的渲染wathcer作为Dep.target。执行完之后会调用this.getter,也就是vm._update(vm._render(), hydrating)

// src/core/observer/watcher.js
export default class Watcher {
  ...
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    ...
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    ...
    // 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()
  },
  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  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
  }
  ...
}

在执行vm._render()的过程中,会执行 vnode = render.call(vm._renderProxy, vm.$createElement)也就是render函数,会访问到模板当中定义的数据,会触发响应式的getter。触发getter,会执行到dep.depend(),也就是 Dep.target.addDep(this),对应到watcher当中就是dep.addSub(this)在dep中,是this.subs.push(sub),那么subs就作为了一个订阅者

addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

watcher的get执行到最后会调用popTarget()来恢复之前的渲染watcher,然后调用 this.cleanupDeps()来清除之前的dep,addDep方法会往newDepIdsnewDeps中添加新的数据,cleanupDeps则会执行this.depIds = this.newDepIds this.deps = this.newDeps然后清空newDepsnewDepIds,然后当下次一进入的时候,会执行dep.removeSub(this)清除之前的数据。可以看到addDep函数中已经做过一层逻辑上的优化,if (!this.newDepIds.has(id))也就是说如果已经有了,那么他不会重复添加,那么为什么还需要执行cleanupDeps是因为当页面的一些数据在上一次数据更新的时候是被依赖的,但是到了新的一次数据更新,他已经不在页面的渲染依赖当中了,也就是说给他不再需要去触发updatecomonent函数了。所以在每次执行完之后,他需要执行cleanupDeps来清除之前的依赖

  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }