現在是時候深入一下了!Vue 最獨特的特性之一,是其非侵入性的響應式系統。數據模型僅僅是普通的 JavaScript 對象。而當你修改它們時,視圖會進行更新。這使得狀態管理非常簡單直接,不過理解其工作原理同樣重要,這樣你可以回避一些常見的問題。在這個章節,我們將進入一些 Vue 響應式系統的底層的細節。
## 如何追蹤變化
當你把一個普通的 JavaScript 對象傳給 Vue 實例的 `data` 選項,Vue 將遍歷此對象所有的屬性,并使用 [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) 把這些屬性全部轉為 [getter/setter](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Working_with_Objects#定義_getters_與_setters)。Object.defineProperty 是 ES5 中一個無法 shim 的特性,這也就是為什么 Vue 不支持 IE8 以及更低版本瀏覽器。
這些 getter/setter 對用戶來說是不可見的,但是在內部它們讓 Vue 追蹤依賴,在屬性被訪問和修改時通知變化。這里需要注意的問題是瀏覽器控制臺在打印數據對象時 getter/setter 的格式化并不同,所以你可能需要安裝 [vue-devtools](https://github.com/vuejs/vue-devtools) 來獲取更加友好的檢查接口。
每個組件實例都有相應的 **watcher** 實例對象,它會在組件渲染的過程中把屬性記錄為依賴,之后當依賴項的 `setter` 被調用時,會通知 `watcher` 重新計算,從而致使它關聯的組件得以更新。

## 檢測變化的注意事項
受現代 JavaScript 的限制 (而且 `Object.observe` 也已經被廢棄),Vue **不能檢測到對象屬性的添加或刪除**。由于 Vue 會在初始化實例時對屬性執行 `getter/setter` 轉化過程,所以屬性必須在 `data` 對象上存在才能讓 Vue 轉換它,這樣才能讓它是響應的。例如:
``` js
var vm = new Vue({
data:{
a:1
}
})
// `vm.a` 是響應的
vm.b = 2
// `vm.b` 是非響應的
```
Vue 不允許在已經創建的實例上動態添加新的根級響應式屬性 (root-level reactive property)。然而它可以使用 `Vue.set(object, key, value)` 方法將響應屬性添加到嵌套的對象上:
``` js
Vue.set(vm.someObject, 'b', 2)
```
您還可以使用 `vm.$set` 實例方法,這也是全局 `Vue.set` 方法的別名:
``` js
this.$set(this.someObject,'b',2)
```
有時你想向一個已有對象添加多個屬性,例如使用 `Object.assign()` 或 `_.extend()` 方法來添加屬性。但是,這樣添加到對象上的新屬性不會觸發更新。在這種情況下可以創建一個新的對象,讓它包含原對象的屬性和新的屬性:
``` js
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
```
也有一些數組相關的問題,之前已經在[列表渲染](list.html#注意事項)中講過。
## 聲明響應式屬性
由于 Vue 不允許動態添加根級響應式屬性,所以你必須在初始化實例前聲明根級響應式屬性,哪怕只是一個空值:
``` js
var vm = new Vue({
data: {
// 聲明 message 為一個空值字符串
message: ''
},
template: '<div>{{ message }}</div>'
})
// 之后設置 `message`
vm.message = 'Hello!'
```
如果你未在 data 選項中聲明 `message`,Vue 將警告你渲染函數正在試圖訪問的屬性不存在。
這樣的限制在背后是有其技術原因的,它消除了在依賴項跟蹤系統中的一類邊界情況,也使 Vue 實例在類型檢查系統的幫助下運行的更高效。而且在代碼可維護性方面也有一點重要的考慮:`data` 對象就像組件狀態的概要,提前聲明所有的響應式屬性,可以讓組件代碼在以后重新閱讀或其他開發人員閱讀時更易于被理解。
## 異步更新隊列
可能你還沒有注意到,Vue **異步**執行 DOM 更新。只要觀察到數據變化,Vue 將開啟一個隊列,并緩沖在同一事件循環中發生的所有數據改變。如果同一個 watcher 被多次觸發,只會被推入到隊列中一次。這種在緩沖時去除重復數據對于避免不必要的計算和 DOM 操作上非常重要。然后,在下一個的事件循環“tick”中,Vue 刷新隊列并執行實際 (已去重的) 工作。Vue 在內部嘗試對異步隊列使用原生的 `Promise.then` 和 `MessageChannel`,如果執行環境不支持,會采用 `setTimeout(fn, 0)` 代替。
例如,當你設置 `vm.someData = 'new value'` ,該組件不會立即重新渲染。當刷新隊列時,組件會在事件循環隊列清空時的下一個“tick”更新。多數情況我們不需要關心這個過程,但是如果你想在 DOM 狀態更新后做點什么,這就可能會有些棘手。雖然 Vue.js 通常鼓勵開發人員沿著“數據驅動”的方式思考,避免直接接觸 DOM,但是有時我們確實要這么做。為了在數據變化之后等待 Vue 完成更新 DOM ,可以在數據變化之后立即使用 `Vue.nextTick(callback)` 。這樣回調函數在 DOM 更新完成后就會調用。例如:
```html
<div id="example">{{message}}</div>
```
``` js
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改數據
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})
```
在組件內使用 `vm.$nextTick()` 實例方法特別方便,因為它不需要全局 `Vue` ,并且回調函數中的 `this` 將自動綁定到當前的 Vue 實例上:
``` js
Vue.component('example', {
template: '<span>{{ message }}</span>',
data: function () {
return {
message: '沒有更新'
}
},
methods: {
updateMessage: function () {
this.message = '更新完成'
console.log(this.$el.textContent) // => '沒有更新'
this.$nextTick(function () {
console.log(this.$el.textContent) // => '更新完成'
})
}
}
})
```
因為 `$nextTick()` 返回一個 Promise 對象,所以你可以使用新的 [ES2016 async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) 語法完成相同的事情:
``` js
methods: {
async updateMessage: function () {
this.message = 'updated'
console.log(this.$el.textContent) // => '未更新'
await this.$nextTick()
console.log(this.$el.textContent) // => '已更新'
}
}
```
- 寫在前面
- 基礎
- 安裝
- 介紹
- Vue實例
- 模板語法
- 計算屬性和偵聽器
- Class 與 Style 綁定
- 條件渲染
- 列表渲染
- 事件處理
- 表單輸入綁定
- 組件基礎
- 深入了解組件
- 組件注冊
- Prop
- 自定義事件
- 插槽
- 動態組件 & 異步組件
- 處理邊界情況
- 過渡 & 動畫
- 進入/離開 & 列表過渡
- 狀態過渡
- 可復用性 & 組合
- 混入
- 自定義指令
- 渲染函數 & JSX
- 插件
- 過濾器
- 工具
- 生產環境部署
- 單文件組件
- 單元測試
- TypeScript 支持
- 規模化
- 路由
- 狀態管理
- 服務端渲染
- 內在
- 深入響應式原理
- 遷移
- 從 Vue 1.x 遷移
- 從 Vue Router 0.7.x 遷移
- 從 Vuex 0.6.x 遷移到 1.0
- 更多
- 對比其他框架
- 加入 Vue.js 社區
- 開發團隊