簡單理解響應式原理

Vue 最獨特的特性之一,是其非侵入性的響應式系統。數據模型僅僅是普通的 JavaScript 對象。而當你修改它們時,視圖會進行更新。這使得狀態管理很是簡單直接,不過理解其工做原理一樣重要,這樣你能夠避開一些常見的問題。html

如何追蹤變化

把一個普通的 JavaScript 對象傳入Vue實例做爲 data 選項,Vue 將遍歷裏面全部的屬性,並使用 Object.defineProperty 把這些屬性所有轉爲 getter/setterObject.defineProperty 是 es5 中一個沒法 shim(Object.defineProperty這個特性是沒法使用低級瀏覽器中的方法來實現) 的特性,這也是 Vue 不支持 IE8 及更低版本瀏覽器的 緣由。vue

這些 getter/setter 對用戶來講是不可見的,可是在內部他們讓 Vue 可以追蹤依賴, 在屬性被訪問和修改時通知變動。數組

每一個組件實例都對應一個 watcher 實例,它會在組件渲染的過程當中把「接觸」過的數據屬性記錄爲依賴。以後當依賴項的 setter 觸發時,會通知 watcher,從而使它關聯的組件從新渲染。瀏覽器

響應示意圖

檢測變化的注意事項

受現代 JavaScript 的限制 (並且 Object.observe 也已經被廢棄),Vue 沒法檢測到對象屬性的添加或刪除。因爲 Vue 會在初始化實例時對屬性執行 getter/setter 轉化,因此屬性必須在 data 對象上存在才能讓 Vue 將它轉換爲響應式的。例如:bash

var vm = new Vue({
  data:{
    a:1
  }
})

// `vm.a` 是響應式的

vm.b = 2
// `vm.b` 是非響應式的
複製代碼

對於已經建立的實例,Vue 不容許動態添加根級別的響應式屬性(實例的一級屬性,data 中的屬性)。可是,可使用 Vue.set(object, propertyName, value) 方法向嵌套對象(對象裏包含對象,如:數組中含有對象元素,對象中含有對象屬性)添加響應式屬性。例如,對於:異步

Vue.set(vm.someObject, 'b', 2)
複製代碼

還可使用 vm.$set 實例方法,這也是全局 Vue.set 方法的別名:async

this.$set(this.someObject,'b',2)
複製代碼

補充: Vue.set/this.$set 的應用 當vue的data裏邊聲明或者已經賦值過的對象或者數組(數組裏邊的值是對象)時,向對象中添加新的屬性,若是更新此屬性的值,是不會更新視圖的。 對於已經建立的實例,Vue 不容許動態添加根級別的響應式屬性,意味着若是在實例建立以後添加新的屬性到實例上,Vue 不能經過 getter/setter 進行轉換,它不會進行響應式處理觸發視圖更新。 如例:在實例中建立屬性 testObjide

<template>
    <div>
        <p >{{testObj.a}}</p>
        <p @click="add(testObj)"> {{testObj.c}}</p>
    </div>
</template>

 <script>
export default {
    data() {
        return {
            testObj: {}
        }
    },
    mounted() {
        this.testObj = { a: 0, b: 1 };
        this.testObj.c = 2;
        console.log(this.testObj, this.testObj.a, this.testObj.b, this.testObj.c);
      },
      methods: {
        add(obj) {
          // obj.a = obj.a + 2;
          obj.c = obj.c + 4;
          console.log(this.testObj, this.testObj.a, this.testObj.b, this.testObj.c);
    },
}
 </script>
複製代碼

控制檯中打印結果: 函數

能夠看到屬性 a 和 b 具備 get、set 方法,c 沒有。點擊c對應的增值方法,界面結果以下: ui

打印結果以下:

能夠看到,在方法中去直接修改 c 屬性,沒有更新視圖,可是值發生了變化,當把add方法裏 obj.a = obj.a + 2;語句放開,視圖中 a、c 都發生變化

打印出的 a、c 值也發生變化:

這時候將mounted方法裏 this.testObj.c = 2; 改爲 this.$set(this.testObj, "c", 2),打印出結果爲:

這時候出現了 get/set,能夠直接修改 c 屬性觸發視圖變化。 當須要新增對象內的響應式屬性時,能夠不關心其餘原有屬性。

數組相關的參考文檔:(cn.vuejs.org/v2/guide/li…)

有時你可能須要爲已有對象賦值多個新屬性,好比使用 Object.assign()_.extend()。 可是,這樣添加到對象上的新屬性不會觸發更新。在這種狀況下,你應該用原對象與要混合進去的對象的屬性一塊兒建立一個新的對象。

// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
複製代碼

聲明響應式屬性

因爲 Vue 不容許動態添加根級響應式屬性,因此你必須在初始化實例前聲明全部根級響應式屬性,哪怕只是一個空值:

var vm = new Vue({
  data: {
    // 聲明 message 爲一個空值字符串
    message: ''
  },
  template: '<div>{{ message }}</div>'
})
// 以後設置 `message`
vm.message = 'Hello!'
複製代碼

若是未在 data 選項中聲明 message,Vue 將警告你渲染函數正在試圖訪問不存在的屬性。

這樣的限制在背後是有其技術緣由的,它消除了在依賴項跟蹤系統中的一類邊界狀況,也使 Vue 實例能更好地配合類型檢查系統工做。但與此同時在代碼可維護性方面也有一點重要的考慮:data 對象就像組件狀態的結構 (schema)。提早聲明全部的響應式屬性,可讓組件代碼在將來修改或給其餘開發人員閱讀時更易於理解。

異步更新隊列

Vue 在更新 DOM 時是異步執行的。只要偵聽到數據變化,Vue 將開啓一個隊列,並緩衝在同一事件循環中發生的全部數據變動。若是同一個 watcher 被屢次觸發,只會被推入到隊列中一次。這種在緩衝時去除重複數據對於避免沒必要要的計算和 DOM 操做是很是重要的。而後,在下一個的事件循環「tick」中,Vue 刷新隊列並執行實際 (已去重的) 工做。Vue 在內部對異步隊列嘗試使用原生的 Promise.thenMutationObserversetImmediate,若是執行環境不支持,則會採用 setTimeout(fn, 0) 代替。

例如,當你設置 vm.someData = 'new value',該組件不會當即從新渲染。當刷新隊列時,組件會在下一個事件循環「tick」中更新。多數狀況咱們不須要關心這個過程,可是若是你想基於更新後的 DOM 狀態來作點什麼,這就可能會有些棘手。雖然 Vue.js 一般鼓勵開發人員使用「數據驅動」的方式思考,避免直接接觸 DOM,可是有時咱們必需要這麼作。爲了在數據變化以後等待 Vue 完成更新 DOM,能夠在數據變化以後當即使用 Vue.nextTick(callback)。這樣回調函數將在 DOM 更新完成後被調用。例如:

<div id="example">{{message}}</div>
複製代碼
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 實例上:

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 語法完成相同的事情:

methods: {
  updateMessage: async function () {
    this.message = '已更新'
    console.log(this.$el.textContent) // => '未更新'
    await this.$nextTick()
    console.log(this.$el.textContent) // => '已更新'
  }
}
複製代碼
相關文章
相關標籤/搜索