Vue会把普通对象变成响应式对象,响应式对象getter相关的逻辑就是依赖收集

defineReactive

  1. /**
  2. * Define a reactive property on an Object.
  3. */
  4. export function defineReactive (
  5. obj: Object,
  6. key: string,
  7. val: any,
  8. customSetter?: ?Function,
  9. shallow?: boolean
  10. ) {
  11. // 实例化一个Deo的实例
  12. const dep = new Dep()
  13. // ...
  14. let childOb = !shallow && observe(val)
  15. Object.defineProperty(obj, key, {
  16. enumerable: true,
  17. configurable: true,
  18. get: function reactiveGetter () {
  19. const value = getter ? getter.call(obj) : val
  20. if (Dep.target) {
  21. dep.depend() // 做依赖收集
  22. if (childOb) {
  23. childOb.dep.depend()
  24. if (Array.isArray(value)) {
  25. dependArray(value)
  26. }
  27. }
  28. }
  29. return value
  30. },
  31. set: function reactiveSetter (newVal) {
  32. // ...
  33. }
  34. })
  35. }

Dep

Dep是整个getter依赖收集的核心
定义在src/core/observer/dep.js中

  1. /* @flow */
  2. import type Watcher from './watcher'
  3. import { remove } from '../util/index'
  4. import config from '../config'
  5. let uid = 0
  6. /**
  7. * A dep is an observable that can have multiple
  8. * directives subscribing to it.
  9. */
  10. export default class Dep {
  11. // 静态属性,全局唯一Watcher
  12. static target: ?Watcher; // 在同一时间只能有一个全局的Watcher被计算
  13. id: number;
  14. subs: Array<Watcher>; // Watcher数组
  15. constructor () {
  16. this.id = uid++
  17. this.subs = []
  18. }
  19. addSub (sub: Watcher) {
  20. this.subs.push(sub)
  21. }
  22. removeSub (sub: Watcher) {
  23. remove(this.subs, sub)
  24. }
  25. depend () {
  26. if (Dep.target) {
  27. Dep.target.addDep(this)
  28. }
  29. }
  30. notify () {
  31. // stabilize the subscriber list first
  32. const subs = this.subs.slice()
  33. if (process.env.NODE_ENV !== 'production' && !config.async) {
  34. // subs aren't sorted in scheduler if not running async
  35. // we need to sort them now to make sure they fire in correct
  36. // order
  37. subs.sort((a, b) => a.id - b.id)
  38. }
  39. for (let i = 0, l = subs.length; i < l; i++) {
  40. subs[i].update()
  41. }
  42. }
  43. }
  44. // The current target watcher being evaluated.
  45. // This is globally unique because only one watcher
  46. // can be evaluated at a time.
  47. Dep.target = null
  48. const targetStack = []
  49. export function pushTarget (target: ?Watcher) {
  50. targetStack.push(target)
  51. Dep.target = target
  52. }
  53. export function popTarget () {
  54. targetStack.pop()
  55. Dep.target = targetStack[targetStack.length - 1]
  56. }

Dep实际上是对Watcher的一种管理,Dep脱离Watcher单独存在是没有意义的

Watcher

Watcher的实现定义在src/core/observer/watcher.js中

  1. let uid = 0
  2. /**
  3. * A watcher parses an expression, collects dependencies,
  4. * and fires callback when the expression value changes.
  5. * This is used for both the $watch() api and directives.
  6. */
  7. export default class Watcher {
  8. vm: Component;
  9. expression: string;
  10. cb: Function;
  11. id: number;
  12. deep: boolean;
  13. user: boolean;
  14. lazy: boolean;
  15. sync: boolean;
  16. dirty: boolean;
  17. active: boolean;
  18. deps: Array<Dep>;
  19. newDeps: Array<Dep>;
  20. depIds: SimpleSet;
  21. newDepIds: SimpleSet;
  22. before: ?Function;
  23. getter: Function;
  24. value: any;
  25. constructor (
  26. vm: Component,
  27. expOrFn: string | Function,
  28. cb: Function,
  29. options?: ?Object,
  30. isRenderWatcher?: boolean
  31. ) {
  32. this.vm = vm
  33. if (isRenderWatcher) {
  34. vm._watcher = this
  35. }
  36. vm._watchers.push(this)
  37. // options
  38. if (options) {
  39. this.deep = !!options.deep
  40. this.user = !!options.user
  41. this.lazy = !!options.lazy
  42. this.sync = !!options.sync
  43. this.before = options.before
  44. } else {
  45. this.deep = this.user = this.lazy = this.sync = false
  46. }
  47. this.cb = cb
  48. this.id = ++uid // uid for batching
  49. this.active = true
  50. this.dirty = this.lazy // for lazy watchers
  51. this.deps = [] // 表示Watcher实例特有的Dep实例的数组
  52. this.newDeps = [] // 表示Watcher实例特有的Dep实例的数组
  53. this.depIds = new Set() // 代表this.deps的id
  54. this.newDepIds = new Set() // 代表this.newDeps的id
  55. this.expression = process.env.NODE_ENV !== 'production'
  56. ? expOrFn.toString()
  57. : ''
  58. // parse expression for getter
  59. if (typeof expOrFn === 'function') {
  60. this.getter = expOrFn
  61. } else {
  62. this.getter = parsePath(expOrFn)
  63. if (!this.getter) {
  64. this.getter = noop
  65. process.env.NODE_ENV !== 'production' && warn(
  66. `Failed watching path: "${expOrFn}" ` +
  67. 'Watcher only accepts simple dot-delimited paths. ' +
  68. 'For full control, use a function instead.',
  69. vm
  70. )
  71. }
  72. }
  73. this.value = this.lazy
  74. ? undefined
  75. : this.get()
  76. }
  77. /**
  78. * Evaluate the getter, and re-collect dependencies.
  79. */
  80. get () {
  81. pushTarget(this)
  82. let value
  83. const vm = this.vm
  84. try {
  85. value = this.getter.call(vm, vm)
  86. } catch (e) {
  87. if (this.user) {
  88. handleError(e, vm, `getter for watcher "${this.expression}"`)
  89. } else {
  90. throw e
  91. }
  92. } finally {
  93. // "touch" every property so they are all tracked as
  94. // dependencies for deep watching
  95. if (this.deep) {
  96. traverse(value)
  97. }
  98. popTarget()
  99. this.cleanupDeps()
  100. }
  101. return value
  102. }
  103. /**
  104. * Add a dependency to this directive.
  105. */
  106. addDep (dep: Dep) {
  107. const id = dep.id
  108. if (!this.newDepIds.has(id)) {
  109. this.newDepIds.add(id)
  110. this.newDeps.push(dep)
  111. if (!this.depIds.has(id)) {
  112. dep.addSub(this)
  113. }
  114. }
  115. }
  116. /**
  117. * Clean up for dependency collection.
  118. */
  119. cleanupDeps () {
  120. let i = this.deps.length
  121. while (i--) {
  122. const dep = this.deps[i]
  123. if (!this.newDepIds.has(dep.id)) {
  124. dep.removeSub(this)
  125. }
  126. }
  127. let tmp = this.depIds
  128. this.depIds = this.newDepIds
  129. this.newDepIds = tmp
  130. this.newDepIds.clear()
  131. tmp = this.deps
  132. this.deps = this.newDeps
  133. this.newDeps = tmp
  134. this.newDeps.length = 0
  135. }
  136. /**
  137. * Subscriber interface.
  138. * Will be called when a dependency changes.
  139. */
  140. update () {
  141. /* istanbul ignore else */
  142. if (this.lazy) {
  143. this.dirty = true
  144. } else if (this.sync) {
  145. this.run()
  146. } else {
  147. queueWatcher(this)
  148. }
  149. }
  150. /**
  151. * Scheduler job interface.
  152. * Will be called by the scheduler.
  153. */
  154. run () {
  155. if (this.active) {
  156. const value = this.get()
  157. if (
  158. value !== this.value ||
  159. // Deep watchers and watchers on Object/Arrays should fire even
  160. // when the value is the same, because the value may
  161. // have mutated.
  162. isObject(value) ||
  163. this.deep
  164. ) {
  165. // set new value
  166. const oldValue = this.value
  167. this.value = value
  168. if (this.user) {
  169. const info = `callback for watcher "${this.expression}"`
  170. invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
  171. } else {
  172. this.cb.call(this.vm, value, oldValue)
  173. }
  174. }
  175. }
  176. }
  177. /**
  178. * Evaluate the value of the watcher.
  179. * This only gets called for lazy watchers.
  180. */
  181. evaluate () {
  182. this.value = this.get()
  183. this.dirty = false
  184. }
  185. /**
  186. * Depend on all deps collected by this watcher.
  187. */
  188. depend () {
  189. let i = this.deps.length
  190. while (i--) {
  191. this.deps[i].depend()
  192. }
  193. }
  194. /**
  195. * Remove self from all dependencies' subscriber list.
  196. */
  197. teardown () {
  198. if (this.active) {
  199. // remove self from vm's watcher list
  200. // this is a somewhat expensive operation so we skip it
  201. // if the vm is being destroyed.
  202. if (!this.vm._isBeingDestroyed) {
  203. remove(this.vm._watchers, this)
  204. }
  205. let i = this.deps.length
  206. while (i--) {
  207. this.deps[i].removeSub(this)
  208. }
  209. this.active = false
  210. }
  211. }
  212. }

过程分析

Vue的mount过程是通过mountComponent函数,有如下一段重要的逻辑

  1. updateComponent = () => {
  2. vm._update(vm._render(), hydrating)
  3. }
  4. new Watcher(vm, updateComponent, noop, {
  5. before () {
  6. if (vm._isMounted) {
  7. callHook(vm, 'beforeUpdate')
  8. }
  9. }
  10. }, true /* isRenderWatcher */)

当实例化一个渲染watcher的时候首先进入watcher的构造函数,然后会执行this.get()方法进入get函数,接着执行

  1. pushTarget(this)

pushTarget方法定义在src/core/observer/dep.js中

  1. // 把Dep.target赋值为当前的渲染watcher并压栈(为了恢复用)
  2. export function pushTarget (target: ?Watcher) {
  3. targetStack.push(target)
  4. Dep.target = target
  5. }

接着又执行

  1. value = this.getter.call(vm, vm) // this.getter对应就是updateComponent函数

实际上就是在执行

  1. vm._update(vm._render(), hydrating) // 会先执行vm.render()方法,这个方法会生成渲染VNode,并且在这个过程中会对vm上的数据进行访问,这个时候就触发了数据对象的getter

每个对象值的getter都持有一个dep,在触发getter的时候会调用dep.depend()方法,也就会执行Dep.target.addDep(this)
Dep.target已经被赋值为渲染watcher,那么就执行到addDep方法

  1. /**
  2. * Add a dependency to this directive.
  3. */
  4. addDep (dep: Dep) {
  5. const id = dep.id
  6. if (!this.newDepIds.has(id)) {
  7. this.newDepIds.add(id)
  8. this.newDeps.push(dep)
  9. if (!this.depIds.has(id)) { // 保证同一数据不会被添加多次
  10. dep.addSub(this)
  11. }
  12. }
  13. }

执行 dep.addSub(this) 后就会执行 this.subs.push(sub),也就是说把当前的watcher订阅到这个数据持有的dep的subs中,这个的目的是为后续数据变化时能通知到哪些subs做准备
在vm.render()过程中会触发所有数据的getter,这样实际上已经完成了一个依赖收集的过程
其实并没有,在完成依赖收集后还有内容

  1. // "touch" every property so they are all tracked as
  2. // dependencies for deep watching
  3. // 递归去访问value,触发它所有子项的getter
  4. if (this.deep) {
  5. traverse(value)
  6. }
  7. popTarget()
  8. this.cleanupDeps()

popTarget方法定义在src/core/observer/dep.js中

  1. // 把Dep.target恢复成上一个状态,因为当前vm的数据依赖收集已经完成,那么对应的渲染Dep.target也需要改变
  2. export function popTarget () {
  3. targetStack.pop()
  4. Dep.target = targetStack[targetStack.length - 1]
  5. }

cleanupDeps依赖清空
Vue 是数据驱动的,所以每次数据变化都会重新 render,那么 vm._render() 方法又会再次执行,并再次触发数据的 getters,所以 Watcher 在构造函数中会初始化 2 个 Dep 实例数组,newDeps 表示新添加的 Dep 实例数组,而 deps 表示上一次添加的 Dep 实例数组

  1. /**
  2. * Clean up for dependency collection.
  3. */
  4. cleanupDeps () {
  5. let i = this.deps.length
  6. while (i--) {
  7. const dep = this.deps[i]
  8. if (!this.newDepIds.has(dep.id)) {
  9. dep.removeSub(this) // 移除对dep.subs数组中Watcher的订阅
  10. }
  11. }
  12. // 把newDepIds和depIds交换
  13. let tmp = this.depIds
  14. this.depIds = this.newDepIds
  15. this.newDepIds = tmp
  16. // 把newDepIds清空
  17. this.newDepIds.clear()
  18. // 把newDeps和deps交换
  19. tmp = this.deps
  20. this.deps = this.newDeps
  21. this.newDeps = tmp
  22. // 把newDeps清空
  23. this.newDeps.length = 0
  24. }

为什么需要做 deps 订阅的移除呢,在添加 deps 的订阅过程,已经能通过 id 去重避免重复订阅了
考虑到一种场景,模板会根据 v-if 去渲染不同子模板 a 和 b,当满足某种条件的时候渲染 a 的时候,会访问到 a 中的数据,这时候对 a 使用的数据添加了 getter,做了依赖收集,那么当去修改 a 的数据的时候,理应通知到这些订阅者。那么如果改变了条件渲染了 b 模板,又会对 b 使用的数据添加了 getter,如果没有依赖移除的过程,那么这时候会去修改 a 模板的数据,会通知 a 数据的订阅的回调,这显然是有浪费的
因此 Vue 设计了在每次添加完新的订阅,会移除掉旧的订阅,这样就保证了在刚才的场景中,如果渲染 b 模板的时候去修改 a 模板的数据,a 数据订阅回调已经被移除了,所以不会有任何浪费