# 依赖搜集原理

基本原理的那一节讲的是怎么监听数据变化的,这一节要讲的是怎么来管理拦截数据的获取以及修改

# 收集者(Dep)

我们可以想象一下,一个数据属性值得变化可能会触发页面的更新,也可能触发一个网络请求,也可能触发一个计算等,那就需要一个收集器去收集这些变化者,举个例子:

<template>
  <input />
  <div>
    firstName: {{name}}
  </div>
</template>

new Vue({
  el: '#app',
  data() {
    name: 'ming xiao'
  },
  computed: {
    firstName() {
      return this.name.split(' ')[0];
    },
  }
  watch: {
    name: function(newVal) {
      fetchDataByName(newVal)
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

那么我们就来实现一个收集器的功能

let uid = 0;
export default class Dep {
  // 收集的目标
  static Target
  constructor() {
    this.id = uid++;
    // 订阅者们
    this.subs = [];
  }
  // 添加订阅者
  addSub(sub) {
    this.subs.push(sub)
  }
  // 删除订阅者
  removeSub(sub) {
    index = this.subs.indexOf(sub);
    if (index !== index) {
      this.subs.splice(index, 1)
    }
  }
  // 这个操作会把dep加入watch的deps里,同时也会把watch加入到dep的subs中
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
  // 通知订阅者更新
  notify() {
    this.subs.forEach(item => {
      item.update();
    })
  }
}

Dep.target = null
const targetStack = []
// 这个方法就是设置当前收集的目标
export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}
// 这个方法就是收集好了目标了,让当前收集到的目标回退到上一个收集的目标
export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

收集器也写好,那么我们怎么用呢,我们就需要和数据的属性拦截串起来,我们要改造一下基本原理那一节中写的defineReactive

function defineReactive(obj, key, value) {
  observe(value);
  // 实例化一个收集器
  const dep = new Dep();
  Object.defineProperty(obj, key, {
    enumberable: false,
    configuralbe: true,
    get: function() {
      if (dep.Target) {
        // 开始收集当前的变化者
        dep.depend(dep.Target)
      }
      return value;
    },
    set: function(newVal) {
      obj[key] = newVal;
      // 通知变化者们,数据有变化了,你们行动起来吧
      dep.notify()
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 变化者(Watch)

我们知道,只要页面用到了data中的数据,那么用到的数据中任何一个属性值数据变化了,视图就跟新了,还有我们的计算属性Computed,一个计算属性的值要依赖大于等于1个data中的属性值来计算,我说这个的意思就是想说,一个变化者有的时候要依赖多个data中的属性值,那么这个就需要一个管理变化者的管理器来管理这些变化者,那我们来看一下Vue中是怎么实现的

let uid = 0
export default class Watcher {
  constructor (
    vm, // vue实例
    expOrFn // 监听data中的属性
    cb, // 变化者的更新动作
    options, // 一些选项参数,比如$watch中的deep
    isRenderWatcher
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this) // 把更新者放到_watchers中
    // options
    if (options) {
      this.deep = !!options.deep // $watch中的deep选项
      this.user = !!options.user // 决定了要不要调用cb,这个$watch中会设置这个参数
      this.lazy = !!options.lazy // 这个lazy参数就是实现了计算属性的开关
      this.sync = !!options.sync // 这个参数确定了下面是要调动queueWatcher还是直接run,我全局搜了一下,这个参数一直都没有出入过,应该一直是false吧
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    // 这里为啥要有两份数据存储dep呢,是为了前后的对比,进而删除不存在的,看cleanupDeps就知道了
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    // parse expression for getter
    if (typeof expOrFn === 'function') { // 计算属性的value是个函数,组件更新也是个函数,都会走到这里
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn) // 这里会返回一个根据路径求值的函数
    }
    this.value = this.lazy // computed的计算属性在没有用到之前是不会计算的
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this) // 设置搜集器的目标是当前的watch
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm) //调用getter会触发数据属性的get拦截器,会收集依赖
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
    
      if (this.deep) { // 这里会一直深度遍历value,然后访问value下的每一个节点,触发每一个节点的get拦截器去收集当前的watch,进而达到每个节点值的变化都会去调用cb
        traverse(value)
      }
      popTarget() //这里搜集完当前watch后,就完成了,需要回退到上一个收集器的目标
      this.cleanupDeps()
    }
    return value
  }

  // 这个方法是由Dep中的depend调动的,该方法会进行双向收集,Dep中收集watch,Watch收集dep
  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)
      }
    }
  }

  // 把newDeps赋值给deps,置空newDeps
  cleanupDeps () {
    let i = this.deps.length
    // 如果不存在,要存dep中移除收集的当前watch(this)
    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
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true   // 这里就是打开computed属性值要重新计算的开关
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this) // 组件的updateComponent和watch都会走这里,这里会异步更新
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) { // $watch的监听方法调用
          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)
        }
      }
    }
  }

  // 这个在lazy watch中会调用,你也可以理解成只要computed中创建的watch才会调用这个方法
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

 // 这个在computed中会调用,目的就是让computed的watch搜集到的dep再去搜集RenderWatch
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  // 当$watch取消时或者组件destrory的时候都会走这个方法,把收集的watch都取消收集
  teardown () {
    if (this.active) {
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173