【手摸手】带你看 Vue2 源码 - 第二章

611 阅读5分钟

依赖收集

src/core/instance/lifecycle.js

之前我们介绍过组件在挂载时执行 $mount 方法,实际执行的是 mountComponent 方法。在这个方法里创建了一个当前实例的渲染 Watcher 对象,第二个参数是 updateComponent 方法,如果该方法被调用会最终执行 _render 方法,_render 会生成 Vnode 并且在这个过程中会产生对实例上数据的访问,也就是触发 getter

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  // ...
  callHook(vm, 'beforeMount')

  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

  /**
   * 创建组件渲染 watcher 并完成依赖收集
   * watcher 调用 updateComponent 进而调用 _render 触发了数据访问
   * 数据的 getter 中的都有 dep,dep 会把当前 watcher 订阅到该 dep 的 subs 中
   */
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

src/core/observer/watcher.js

Watcher 的构造方法里会调用 get 方法

/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
    // 当前 watcher 赋值给 Dep.target 
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // src/core/instance/lifecycle.js => updateComponent 触发了数据 getter 中 dep
      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
}

首先关注头尾的 pushTargetpopTarget 方法,这两个方法保证了在同一时间内只有一个 Watcher 被处理。参数可以为空是因为在某些场景下如错误处理时,需要访问实例上的数据,此时不需要添加 Dep 依赖。

value = this.getter.call(vm, vm) 这句话其实就是调用了上面提到的 updateComponent 方法,调用后会触发数据的 getter

// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null
const targetStack = []

export function pushTarget (target: ?Watcher) {
  targetStack.push(target) // 恢复用
  Dep.target = target // 当前 watcher
}

export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

src/core/observer/index.js

上篇文章介绍了 initState 方法,在 initState 调用了 defineReactivedataprops 做了响应处理。

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep() // 依赖

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      // 发生数据访问时,如果 Dep.target 存在,那么 Dep.target 就是当前渲染 watcher
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify() // dep 通知所有组件 watcher
    }
  })
}

getter 中先是判断了 Dep.target 当前对应的 Watcher 是否存在,然后调用 dep.depend() 去进行了依赖收集,告诉针对这个数据的 Dep 把当前 Watcher 添加进来,用于以后通知更新变化。

src/core/observer/dep.js

实际上 depwatcher 是互相添加多对多的关系,到此 Dep 已经添加完需要观测当前数据变化的所有 Watcher,依赖收集完毕。

  • Dep

    addSub (sub: Watcher) {
      this.subs.push(sub)
    }
    
    depend () {
      if (Dep.target) {
        // 实际就是 watcher.addDep 把当前 dep 添加到 watcher
        // watcher.addDep 也会调用 dep.addSub(this) 把 watcher 添加到 dep 中
        Dep.target.addDep(this)
      }
    }
    
  • Watcher

    /**
    * Add a dependency to this directive.
    */
    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 添加到 watcher
          dep.addSub(this)
        }
      }
    }
    

派发更新

当数据发生改变的时候会触发 setter 方法,该方法中会调用 dep.notify() 去通知所有观测此数据的 Watcher

src/core/observer/dep.js

notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort((a, b) => a.id - b.id)
    }
    // subs 存放所有的 watcher 实例
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

src/core/observer/watcher.js

update 一般会进入 queueWatcher, 将 Watcher 加入一个任务队列。

/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
  /* istanbul ignore else */
  if (this.lazy) { // 计算属性
    this.dirty = true // 依赖变化 计算属性需要重新计算
  } else if (this.sync) { // 强制同步更新
    this.run()
  } else {
    // watcher 入队
    queueWatcher(this)
  }
}

src/core/observer/scheduler.js

queueWatcher 在进行去重后会调用 nextTick 异步的执行队列。每个 watcher 调用 run 方法去触发更新。

  • queueWatcher

    /**
     * Push a watcher into the watcher queue.
     * Jobs with duplicate IDs will be skipped unless it's
     * pushed when the queue is being flushed.
     */
    export function queueWatcher (watcher: Watcher) {
      const id = watcher.id
      // 不存在才入队伍,去重
      if (has[id] == null) {
        has[id] = true
        if (!flushing) {
          queue.push(watcher)
        } else {
          // if already flushing, splice the watcher based on its id
          // if already past its id, it will be run next immediately.
          // 按照 id 把当前 watcher 添加进队列,id 越小越优先
          let i = queue.length - 1
          while (i > index && queue[i].id > watcher.id) {
            i--
          }
          queue.splice(i + 1, 0, watcher)
        }
        // queue the flush
        if (!waiting) {
          waiting = true
    
          if (process.env.NODE_ENV !== 'production' && !config.async) {
            flushSchedulerQueue()
            return
          }
          // 异步执行 flushSchedulerQueue
          nextTick(flushSchedulerQueue)
        }
      }
    }
    
  • flushSchedulerQueue

    /**
     * Flush both queues and run the watchers.
     */
    function flushSchedulerQueue () {
      currentFlushTimestamp = getNow()
      flushing = true
      let watcher, id
    
      // Sort queue before flush.
      // This ensures that:
      // 1. Components are updated from parent to child. (because parent is always
      //    created before the child)
      // 2. A component's user watchers are run before its render watcher (because
      //    user watchers are created before the render watcher)
      // 3. If a component is destroyed during a parent component's watcher run,
      //    its watchers can be skipped.
      // 1. 组件是从父到子更新,因为父组件先与子组件创建
      // 2. 用户使用的 watcher api 优先于组件的渲染 watcher,因为是在渲染 watcher 之前创建的
      // 3. 如果一个组件在父组件 watcher 执行时被销毁,则该组件的 watcher 将被跳过
      queue.sort((a, b) => a.id - b.id)
    
      // do not cache length because more watchers might be pushed
      // as we run existing watchers
      for (index = 0; index < queue.length; index++) {
        // 每次拿出一个 watcher
        watcher = queue[index]
        if (watcher.before) {
          watcher.before() // callHook(vm, 'beforeUpdate')
        }
        id = watcher.id
        has[id] = null // 移除将要处理的 watcher
        watcher.run() // 触发更新
        
      }
    
      // keep copies of post queues before resetting state
      const activatedQueue = activatedChildren.slice()
      const updatedQueue = queue.slice()
    
      resetSchedulerState()
    
      // call component updated and activated hooks
      callActivatedHooks(activatedQueue)
      callUpdatedHooks(updatedQueue)
    }
    

src/core/observer/watcher.js

最终回到 Watcher 中,Watcher 分为渲染和用户自定义两种,目前我们针对的是渲染 watcer ,最终会再次调用 updateComponent 进行 patch 后生成渲染函数。自定义就是 watcher api

/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run () {
    if (this.active) {
      // 对于渲染 watcher 来说 => this.getter.call(vm, vm) => updateComponent => 重新生成渲染函数并进行 patch
      // updateComponent 会触发 getter 重新添加 watcher
      const value = this.get()
      // 用户 watcher 会有返回值,再执行下面
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        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)
        }
      }
    }
}