## 響應式系統
Vue.js 是一款 MVVM 框架,數據模型僅僅是普通的 JavaScript 對象,但是對這些對象進行操作時,卻能影響對應視圖,它的核心實現就是「**響應式系統**」。盡管我們在使用 Vue.js 進行開發時不會直接修改「**響應式系統**」,但是理解它的實現有助于避開一些常見的「**坑**」,也有助于在遇見一些琢磨不透的問題時可以深入其原理來解決它。
## `Object.defineProperty`
首先我們來介紹一下 [`Object.defineProperty`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty),Vue.js就是基于它實現「**響應式系統**」的。
首先是使用方法:
```
/*
obj: 目標對象
prop: 需要操作的目標對象的屬性名
descriptor: 描述符
return value 傳入對象
*/
Object.defineProperty(obj, prop, descriptor)
```
descriptor的一些屬性,簡單介紹幾個屬性,具體可以參考 [MDN 文檔](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)。
* `enumerable`,屬性是否可枚舉,默認 false。
* `configurable`,屬性是否可以被修改或者刪除,默認 false。
* `get`,獲取屬性的方法。
* `set`,設置屬性的方法。
## 實現 `observer`(可觀察的)
知道了 `Object.defineProperty` 以后,我們來用它使對象變成可觀察的。
這一部分的內容我們在第二小節中已經初步介紹過,在 `init` 的階段會進行初始化,對數據進行「**響應式化**」。

為了便于理解,我們不考慮數組等復雜的情況,只對對象進行處理。
首先我們定義一個 `cb` 函數,這個函數用來模擬視圖更新,調用它即代表更新視圖,內部可以是一些更新視圖的方法。
```
function cb (val) {
/* 渲染視圖 */
console.log("視圖更新啦~");
}
```
然后我們定義一個 `defineReactive` ,這個方法通過 `Object.defineProperty` 來實現對對象的「**響應式**」化,入參是一個 obj(需要綁定的對象)、key(obj的某一個屬性),val(具體的值)。經過 `defineReactive` 處理以后,我們的 obj 的 key 屬性在「讀」的時候會觸發 `reactiveGetter` 方法,而在該屬性被「寫」的時候則會觸發 `reactiveSetter` 方法。
```
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true, /* 屬性可枚舉 */
configurable: true, /* 屬性可被修改或刪除 */
get: function reactiveGetter () {
return val; /* 實際上會依賴收集,下一小節會講 */
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
cb(newVal);
}
});
}
```
當然這是不夠的,我們需要在上面再封裝一層 `observer` 。這個函數傳入一個 value(需要「**響應式**」化的對象),通過遍歷所有屬性的方式對該對象的每一個屬性都通過 `defineReactive` 處理。(注:實際上 observer 會進行遞歸調用,為了便于理解去掉了遞歸的過程)
```
function observer (value) {
if (!value || (typeof value !== 'object')) {
return;
}
Object.keys(value).forEach((key) => {
defineReactive(value, key, value[key]);
});
}
```
最后,讓我們用 `observer` 來封裝一個 Vue 吧!
在 Vue 的構造函數中,對 `options` 的 `data` 進行處理,這里的 `data` 想必大家很熟悉,就是平時我們在寫 Vue 項目時組件中的 `data` 屬性(實際上是一個函數,這里當作一個對象來簡單處理)。
```
class Vue {
/* Vue構造類 */
constructor(options) {
this._data = options.data;
observer(this._data);
}
}
```
這樣我們只要 new 一個 Vue 對象,就會將 `data` 中的數據進行「**響應式**」化。如果我們對 `data` 的屬性進行下面的操作,就會觸發 `cb` 方法更新視圖。
```
let o = new Vue({
data: {
test: "I am test."
}
});
o._data.test = "hello,world."; /* 視圖更新啦~ */
```
至此,響應式原理已經介紹完了,接下來讓我們學習「**響應式系統**」的另一部分 ——「**依賴收集**」。
注:本節代碼參考[《響應式系統的基本原理》](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%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86%E3%80%8B.js)。