Vue源码阅读笔记—— Dep、Watcher类及响应式原理小结

151 阅读2分钟

前言

所看的源码版本是:"version": "2.6.14"

Dep

Dep你可以理解为是一个依赖收集器,用来收集watcher的。它里面代码很简单我们一起看看(src/core/observer/dep.js)

import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'

let uid = 0

/**
 * 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 = [] // 用来收集watcher
  }

  // 添加watcher
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  // 移除watcher
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  // 依赖收集  Dep.target是一个watcher 当前存在的时候就将它放入观察者列表当中
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
  // 通知订阅者  做更新操作
  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)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// 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 = []

// 下面主要就是 修改Dep.target的指向

export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

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

Dep类代码还是比较简单的,主要就是几个关键方法。

Watcher

Watcher是一个观察者,主要工作是当观察到有数据变动时,回去执行传入的回调函数做一些更新操作。

Watcher我觉得源码相对来说还是不太好理解的,我们先看几个主要的代码,先主要理解它是做啥的就行。(src/core/observer/watcher.js)

当数据劫持触发set时,会去触发Watcherupdate方法:

update () {
    /* istanbul ignore else */
    if (this.lazy) { // 计算属性watcher处理逻辑
      this.dirty = true
    } else if (this.sync) { // 设置sync同步会马上执行run 方法
      this.run()
    } else {
       // 加入到异步watcher队列做处理,源码位置src/core/observer/scheduler.js 主要是让其在nextTick去执操作  最后还是要执行watcher.run()方法
      queueWatcher(this)
    }
  }
  
// run 方法主要执行了get和cb方法
run () {
    if (this.active) {
      const value = this.get()
      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) {
          const info = `callback for watcher "${this.expression}"`
          invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }
  
  // get一方面和dep建立联系;其次通过getter获取了值
  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
  }

总结

  • 结合之前我们学习的Observer,我们知道了如何对数据进行劫持,在get的时候我们知道哪些地方使用了数据,set的时候我们知道数据变化了,需要通知那些依赖此数据的地方做更新。

  • 对于get时依赖数据的地方我们把它看作一个watcher,在get数据的时候我们通过dep依赖收集器把这些watcher收集起来放在一个数组里面。

  • 当数据变化触发set时,我们执行dep.notify()去通知watcher做更新操作。