## 為什么要依賴收集?
**先舉個栗子??**
我們現在有這么一個 Vue 對象。
```
new Vue({
template:
`<div>
<span>{{text1}}</span>
<span>{{text2}}</span>
<div>`,
data: {
text1: 'text1',
text2: 'text2',
text3: 'text3'
}
});
```
然后我們做了這么一個操作。
```
this.text3 = 'modify text3';
```
我們修改了 `data` 中 `text3` 的數據,但是因為視圖中并不需要用到 `text3` ,所以我們并不需要觸發上一章所講的 `cb` 函數來更新視圖,調用 `cb` 顯然是不正確的。
**再來一個栗子??**
假設我們現在有一個全局的對象,我們可能會在多個 Vue 對象中用到它進行展示。
```
let globalObj = {
text1: 'text1'
};
let o1 = new Vue({
template:
`<div>
<span>{{text1}}</span>
<div>`,
data: globalObj
});
let o2 = new Vue({
template:
`<div>
<span>{{text1}}</span>
<div>`,
data: globalObj
});
```
這個時候,我們執行了如下操作。
```
globalObj.text1 = 'hello,text1';
```
我們應該需要通知 `o1` 以及 `o2` 兩個vm實例進行視圖的更新,「依賴收集」會讓 `text1` 這個數據知道“哦~有兩個地方依賴我的數據,我變化的時候需要通知它們~”。
最終會形成數據與視圖的一種對應關系,如下圖。

接下來我們來介紹一下「依賴收集」是如何實現的。
## 訂閱者 Dep
首先我們來實現一個訂閱者 Dep ,它的主要作用是用來存放 `Watcher` 觀察者對象。
```
class Dep {
constructor () {
/* 用來存放Watcher對象的數組 */
this.subs = [];
}
/* 在subs中添加一個Watcher對象 */
addSub (sub) {
this.subs.push(sub);
}
/* 通知所有Watcher對象更新視圖 */
notify () {
this.subs.forEach((sub) => {
sub.update();
})
}
}
```
為了便于理解我們只實現了添加的部分代碼,主要是兩件事情:
1. 用 `addSub` 方法可以在目前的 `Dep` 對象中增加一個 `Watcher` 的訂閱操作;
2. 用 `notify` 方法通知目前 `Dep` 對象的 `subs` 中的所有 `Watcher` 對象觸發更新操作。
## 觀察者 Watcher
```
class Watcher {
constructor () {
/* 在new一個Watcher對象時將該對象賦值給Dep.target,在get中會用到 */
Dep.target = this;
}
/* 更新視圖的方法 */
update () {
console.log("視圖更新啦~");
}
}
Dep.target = null;
```
## 依賴收集
接下來我們修改一下 `defineReactive` 以及 Vue 的構造函數,來完成依賴收集。
我們在閉包中增加了一個 Dep 類的對象,用來收集 `Watcher` 對象。在對象被「讀」的時候,會觸發 `reactiveGetter` 函數把當前的 `Watcher` 對象(存放在 Dep.target 中)收集到 `Dep` 類中去。之后如果當該對象被「**寫**」的時候,則會觸發 `reactiveSetter` 方法,通知 `Dep` 類調用 `notify` 來觸發所有 `Watcher` 對象的 `update` 方法更新對應視圖。
```
function defineReactive (obj, key, val) {
/* 一個Dep類對象 */
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
/* 將Dep.target(即當前的Watcher對象存入dep的subs中) */
dep.addSub(Dep.target);
return val;
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
/* 在set的時候觸發dep的notify來通知所有的Watcher對象更新視圖 */
dep.notify();
}
});
}
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data);
/* 新建一個Watcher觀察者對象,這時候Dep.target會指向這個Watcher對象 */
new Watcher();
/* 在這里模擬render的過程,為了觸發test屬性的get函數 */
console.log('render~', this._data.test);
}
}
```
## 小結
總結一下。
首先在 `observer` 的過程中會注冊 `get` 方法,該方法用來進行「**依賴收集**」。在它的閉包中會有一個 `Dep` 對象,這個對象用來存放 Watcher 對象的實例。其實「**依賴收集**」的過程就是把 `Watcher` 實例存放到對應的 `Dep` 對象中去。`get` 方法可以讓當前的 `Watcher` 對象(Dep.target)存放到它的 subs 中(`addSub`)方法,在數據變化時,`set` 會調用 `Dep` 對象的 `notify` 方法通知它內部所有的 `Watcher` 對象進行視圖更新。
這是 `Object.defineProperty` 的 `set/get` 方法處理的事情,那么「**依賴收集**」的前提條件還有兩個:
1. 觸發 `get` 方法;
2. 新建一個 Watcher 對象。
這個我們在 Vue 的構造類中處理。新建一個 `Watcher` 對象只需要 new 出來,這時候 `Dep.target` 已經指向了這個 new 出來的 `Watcher` 對象來。而觸發 `get` 方法也很簡單,實際上只要把 render function 進行渲染,那么其中的依賴的對象都會被「讀取」,這里我們通過打印來模擬這個過程,讀取 test 來觸發 `get` 進行「依賴收集」。
本章我們介紹了「依賴收集」的過程,配合之前的響應式原理,已經把整個「響應式系統」介紹完畢了。其主要就是 `get` 進行「依賴收集」。`set` 通過觀察者來更新視圖,配合下圖仔細捋一捋,相信一定能搞懂它!

注:本節代碼參考[《響應式系統的依賴收集追蹤原理》](https://github.com/answershuto/VueDemo/blob/master/%E3%80%8A%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F%E7%9A%84%E4%BE%9D%E8%B5%96%E6%94%B6%E9%9B%86%E8%BF%BD%E8%B8%AA%E5%8E%9F%E7%90%86%E3%80%8B.js)。