Vue.js每天必學(xué)之內(nèi)部響應(yīng)式原理探究
深入響應(yīng)式原理
大部分的基礎(chǔ)內(nèi)容我們已經(jīng)講到了,現(xiàn)在講點底層內(nèi)容。Vue.js 最顯著的一個功能是響應(yīng)系統(tǒng) —— 模型只是普通對象,修改它則更新視圖。這讓狀態(tài)管理非常簡單且直觀,不過理解它的原理也很重要,可以避免一些常見問題。下面我們開始深挖 Vue.js 響應(yīng)系統(tǒng)的底層細(xì)節(jié)。
如何追蹤變化
把一個普通對象傳給 Vue 實例作為它的 data 選項,Vue.js 將遍歷它的屬性,用 Object.defineProperty 將它們轉(zhuǎn)為 getter/setter。這是 ES5 特性,不能打補(bǔ)丁實現(xiàn),這便是為什么 Vue.js 不支持 IE8 及更低版本。
用戶看不到 getter/setters,但是在內(nèi)部它們讓 Vue.js 追蹤依賴,在屬性被訪問和修改時通知變化。一個問題是在瀏覽器控制臺打印數(shù)據(jù)對象時 getter/setter 的格式化不同,使用 vm.$log() 實例方法可以得到更友好的輸出。
模板中每個指令/數(shù)據(jù)綁定都有一個對應(yīng)的 watcher 對象,在計算過程中它把屬性記錄為依賴。之后當(dāng)依賴的 setter 被調(diào)用時,會觸發(fā) watcher 重新計算 ,也就會導(dǎo)致它的關(guān)聯(lián)指令更新 DOM。
變化檢測問題
受 ES5 的限制,Vue.js 不能檢測到對象屬性的添加或刪除。因為 Vue.js 在初始化實例時將屬性轉(zhuǎn)為 getter/setter,所以屬性必須在 data 對象上才能讓 Vue.js 轉(zhuǎn)換它,才能讓它是響應(yīng)的。例如:
var data = { a: 1 } var vm = new Vue({ data: data }) // `vm.a` 和 `data.a` 現(xiàn)在是響應(yīng)的 vm.b = 2 // `vm.b` 不是響應(yīng)的 data.b = 2 // `data.b` 不是響應(yīng)的
不過,有辦法在實例創(chuàng)建之后添加屬性并且讓它是響應(yīng)的。
對于 Vue 實例,可以使用 $set(key, value) 實例方法:
vm.$set('b', 2) // `vm.b` 和 `data.b` 現(xiàn)在是響應(yīng)的
對于普通數(shù)據(jù)對象,可以使用全局方法 Vue.set(object, key, value):
Vue.set(data, 'c', 3) // `vm.c` 和 `data.c` 現(xiàn)在是響應(yīng)的
有時你想向已有對象上添加一些屬性,例如使用 Object.assign() 或 _.extend() 添加屬性。但是,添加到對象上的新屬性不會觸發(fā)更新。這時可以創(chuàng)建一個新的對象,包含原對象的屬性和新的屬性:
// 不使用 `Object.assign(this.someObject, { a: 1, b: 2 })` this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
也有一些數(shù)組相關(guān)的問題,之前已經(jīng)在列表渲染中講過。
初始化數(shù)據(jù)
盡管 Vue.js 提供了 API 動態(tài)地添加響應(yīng)屬性,還是推薦在 data 對象上聲明所有的響應(yīng)屬性。
不這么做:
var vm = new Vue({ template: '<div>{{msg}}</div>' }) // 然后添加 `msg` vm.$set('msg', 'Hello!')
這么做:
var vm = new Vue({ data: { // 以一個空值聲明 `msg` msg: '' }, template: '<div>{{msg}}</div>' }) // 然后設(shè)置 `msg` vm.msg = 'Hello!'
這么做有兩個原因:
1.data 對象就像組件狀態(tài)的模式(schema)。在它上面聲明所有的屬性讓組件代碼更易于理解。
2.添加一個頂級響應(yīng)屬性會強(qiáng)制所有的 watcher 重新計算,因為它之前不存在,沒有 watcher 追蹤它。這么做性能通常是可以接受的(特別是對比 Angular 的臟檢查),但是可以在初始化時避免。
異步更新隊列
Vue.js 默認(rèn)異步更新 DOM。每當(dāng)觀察到數(shù)據(jù)變化時,Vue 就開始一個隊列,將同一事件循環(huán)內(nèi)所有的數(shù)據(jù)變化緩存起來。如果一個 watcher 被多次觸發(fā),只會推入一次到隊列中。等到下一次事件循環(huán),Vue 將清空隊列,只進(jìn)行必要的 DOM 更新。在內(nèi)部異步隊列優(yōu)先使用 MutationObserver,如果不支持則使用 setTimeout(fn, 0)。
例如,設(shè)置了 vm.someData = 'new value',DOM 不會立即更新,而是在下一次事件循環(huán)清空隊列時更新。我們基本不用關(guān)心這個過程,但是如果想在 DOM 狀態(tài)更新后做點什么,這會有幫助。盡管 Vue.js 鼓勵開發(fā)者沿著數(shù)據(jù)驅(qū)動的思路,避免直接修改 DOM,但是有時確實要這么做。為了在數(shù)據(jù)變化之后等待 Vue.js 完成更新 DOM,可以在數(shù)據(jù)變化之后立即使用 Vue.nextTick(callback) ?;卣{(diào)在 DOM 更新完成后調(diào)用。例如:
<div id="example">{{msg}}</div>
var vm = new Vue({ el: '#example', data: { msg: '123' } }) vm.msg = 'new message' // 修改數(shù)據(jù) vm.$el.textContent === 'new message' // false Vue.nextTick(function () { vm.$el.textContent === 'new message' // true })
vm.$nextTick() 這個實例方法比較方便,因為它不需要全局 Vue,它的回調(diào)的 this 自動綁定到當(dāng)前 Vue 實例:
Vue.component('example', { template: '<span>{{msg}}</span>', data: function () { return { msg: 'not updated' } }, methods: { updateMessage: function () { this.msg = 'updated' console.log(this.$el.textContent) // => 'not updated' this.$nextTick(function () { console.log(this.$el.textContent) // => 'updated' }) } } })
計算屬性的奧秘
你應(yīng)該注意到 Vue.js 的計算屬性不是簡單的 getter。計算屬性持續(xù)追蹤它的響應(yīng)依賴。在計算一個計算屬性時,Vue.js 更新它的依賴列表并緩存結(jié)果,只有當(dāng)其中一個依賴發(fā)生了變化,緩存的結(jié)果才無效。因此,只要依賴不發(fā)生變化,訪問計算屬性會直接返回緩存的結(jié)果,而不是調(diào)用 getter。
為什么要緩存呢?假設(shè)我們有一個高耗計算屬性 A,它要遍歷一個巨型數(shù)組并做大量的計算。然后,可能有其它的計算屬性依賴 A。如果沒有緩存,我們將調(diào)用 A 的 getter 許多次,超過必要次數(shù)。
由于計算屬性被緩存了,在訪問它時 getter 不總是被調(diào)用。考慮下例:
var vm = new Vue({ data: { msg: 'hi' }, computed: { example: function () { return Date.now() + this.msg } } })
計算屬性 example 只有一個依賴:vm.msg。Date.now() 不是 響應(yīng)依賴,因為它跟 Vue 的數(shù)據(jù)觀察系統(tǒng)無關(guān)。因而,在訪問 vm.example 時將發(fā)現(xiàn)時間戳不變,除非 vm.msg 變了。
有時希望 getter 不改變原有的行為,每次訪問 vm.example 時都調(diào)用 getter。這時可以為指定的計算屬性關(guān)閉緩存:
computed: { example: { cache: false, get: function () { return Date.now() + this.msg } } }
現(xiàn)在每次訪問 vm.example 時,時間戳都是新的。但是,只是在 JavaScript 中訪問是這樣的;數(shù)據(jù)綁定仍是依賴驅(qū)動的。如果在模塊中這樣綁定計算屬性 {{example}},只有響應(yīng)依賴發(fā)生變化時才更新 DOM。
本文已被整理到了《Vue.js前端組件學(xué)習(xí)教程》,歡迎大家學(xué)習(xí)閱讀。
關(guān)于vue.js組件的教程,請大家點擊專題vue.js組件學(xué)習(xí)教程進(jìn)行學(xué)習(xí)。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Vue項目配置、切換主題顏色詳細(xì)教程(mixin+scss方式,簡單高效)
這篇文章主要給大家介紹了關(guān)于Vue項目配置、切換主題顏色(mixin+scss方式)的相關(guān)資料,根據(jù)預(yù)設(shè)的配色方案,在前端實現(xiàn)動態(tài)切換系統(tǒng)主題顏色,文中通過代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11vue-quill-editor插入圖片路徑太長問題解決方法
這篇文章主要介紹了vue-quill-editor插入圖片路徑太長問題解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01vant如何實現(xiàn)Collapse折疊面板標(biāo)題自定義
這篇文章主要介紹了vant如何實現(xiàn)Collapse折疊面板標(biāo)題自定義,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04vue項目實現(xiàn)點擊目標(biāo)區(qū)域之外可關(guān)閉(隱藏)目標(biāo)區(qū)域
這篇文章主要介紹了vue項目實現(xiàn)點擊目標(biāo)區(qū)域之外可關(guān)閉(隱藏)目標(biāo)區(qū)域,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03Vue Echarts實現(xiàn)圖表的動態(tài)適配以及如何優(yōu)化
這篇文章主要介紹了Vue Echarts實現(xiàn)圖表的動態(tài)適配以及如何優(yōu)化,在實際的前端開發(fā)過程中,動態(tài)適配是一個非常重要的問題,在數(shù)據(jù)可視化的場景下,圖表的動態(tài)適配尤為重要,需要的朋友可以參考下2023-05-05