一筹我、前言
“響應(yīng)式”是 Vue 的一大特點(diǎn),當(dāng)我們?cè)诟鶎?shí)例的 data 中定義了數(shù)據(jù)后,在模板中使用 {{}} 的語法便可以使用其中的數(shù)據(jù)了九孩,且在之后的編碼中我們無需關(guān)心其視圖層面的表現(xiàn)名段,可以將精力集中處理業(yè)務(wù)邏輯與數(shù)據(jù)阱扬。因?yàn)?Vue 會(huì)代替我們監(jiān)聽數(shù)據(jù)的變化,并在視圖層同步我們更新的數(shù)據(jù)伸辟。那 Vue 是如何實(shí)現(xiàn)對(duì)數(shù)據(jù)的監(jiān)聽與使用的呢麻惶?例如對(duì)象的屬性,數(shù)組的項(xiàng)信夫。
二窃蹋、數(shù)據(jù)偵測(cè)
(一)偵測(cè) Object
1. Object.defineProperty(obj, prop, descriptor)
該方法用于設(shè)置對(duì)象屬性,其接受 3 個(gè)參數(shù)静稻,第一個(gè)參數(shù)是需要定義的對(duì)象警没,第二個(gè)是需要定義的屬性,第三個(gè)則被稱為描述符振湾。JS 中的對(duì)象身上有兩種描述符杀迹,第一種是數(shù)據(jù)描述符,具有以下鍵值:
- configurable押搪,用于描述數(shù)據(jù)的可配置性树酪,是否可通過 delete 操作符刪除,默認(rèn)值為 false大州;
- enumerable续语,表示該屬性是否可枚舉,即可通過 for...in 訪問其屬性摧茴,默認(rèn)值為 false绵载;
- value,表示該屬性的值苛白,可以是任何有效的 JS 值娃豹,默認(rèn)值為 undefined;
- writable购裙,表示該屬性的值是否可寫懂版,默認(rèn)值為 false,當(dāng)值為 true 時(shí)其值才可被賦值運(yùn)算符改變躏率。
第二種是訪問器描述符躯畴,具有以下鍵值:
- configurable民鼓,用于描述數(shù)據(jù)的可配置性,是否可通過 delete 操作符刪除蓬抄,默認(rèn)值為 false丰嘉;
- enumerable,表示該屬性是否可枚舉嚷缭,即可通過 for...in 訪問其屬性饮亏,默認(rèn)值為 false;
- getter阅爽,在讀取屬性時(shí)調(diào)用的函數(shù)路幸,默認(rèn)值為 undefined;
- setter付翁,在寫入屬性時(shí)調(diào)用的函數(shù)简肴,默認(rèn)值為 undefined。
這兩種描述符不可以同時(shí)使用百侧。同時(shí)砰识,請(qǐng)注意 getter 與 setter 兩個(gè)方法,一個(gè)是讀取對(duì)象屬性時(shí)調(diào)用移层,一個(gè)是寫入時(shí)調(diào)用仍翰。這意味著對(duì)象的屬性是可“偵測(cè)”的。
2. 數(shù)據(jù)響應(yīng)
現(xiàn)在观话,對(duì)象屬性已經(jīng)是可以偵測(cè)的了予借,但實(shí)現(xiàn)視圖層的同步更新是一個(gè)問題。
其實(shí)频蛔,數(shù)據(jù)雖多灵迫,但只需在使用數(shù)據(jù)的位置進(jìn)行更新即可。前面也說到晦溪,getter 是一個(gè)屬性被讀取時(shí)調(diào)用的函數(shù)瀑粥,通過它,Vue 知道了誰讀取了該屬性三圆,順即將其存在getter中狞换。同樣的,誰觸發(fā)了 setter 方法舟肉,由 setter 去通知依賴了該屬性的元素更新就好修噪。但Vue中的實(shí)現(xiàn)并非這么簡(jiǎn)單,Vue 建立了一個(gè) Watcher路媚,其代替 getter 行使收集那些依賴了數(shù)據(jù)的元素的職能黄琼,誰使用了數(shù)據(jù),便為其建立一個(gè) watcher 實(shí)例整慎,當(dāng)數(shù)據(jù)更新時(shí)脏款,由這個(gè) watcher 通知元素更新數(shù)據(jù)围苫。但這個(gè)通知不是直接通知,Vue是通過虛擬 Dom 實(shí)現(xiàn)真實(shí) Dom 的更新撤师,這個(gè) watcher 通知渲染函數(shù)剂府,再由渲染函數(shù)操作虛擬 Dom 實(shí)現(xiàn)視圖的更新。
3. 屬性的使用
Vue 無法檢測(cè) property 的添加或移除剃盾。由于 Vue 會(huì)在初始化實(shí)例時(shí)對(duì) property 執(zhí)行 getter周循、setter 轉(zhuǎn)化,所以 property 必須在 data 對(duì)象上存在才能讓 Vue 將它轉(zhuǎn)換為響應(yīng)式的万俗。
(二)偵測(cè) Array
1. 利用對(duì)象的 getter 與 setter
Array 類型并不同于 Object 類型,并沒有 getter 與 setter,其并不能使用這兩種方法饮怯。但巧妙的是闰歪,data 里 return 的就是一個(gè)對(duì)象,數(shù)組存放在其中蓖墅,恰作為 data 的一個(gè)屬性库倘,誰讀取它,便可以被偵測(cè)到了论矾。但這僅僅是偵測(cè)到 array 的使用者教翩,array 的自身的變化如何偵測(cè)?
2. 操作即變化
對(duì)于數(shù)組而言贪壳,數(shù)組的變化便表明使用者調(diào)用了數(shù)組方法饱亿,例如 push(),pop() 等闰靴。明確了這個(gè)概念彪笼,Vue 便可以實(shí)現(xiàn) Array 的偵測(cè)。其將可以改變數(shù)組的方法(push蚂且,pop配猫,shift,unshift杏死,splice泵肄,sort,reverse)進(jìn)行了從新封裝淑翼,在不改變這些方法的原始功能的基礎(chǔ)上安插了偵測(cè)器(攔截器)腐巢,一旦調(diào)用這些方法,意味著數(shù)組的改變窒舟,這些偵聽器便會(huì)通知Vue數(shù)組發(fā)生了變化系忙。通過實(shí)現(xiàn)一個(gè)存儲(chǔ)那些使用了數(shù)組的依賴存儲(chǔ)器,再實(shí)現(xiàn)一個(gè)訪問通知這些依賴的方法惠豺,便實(shí)現(xiàn)了數(shù)組的偵測(cè)银还。
3. 彌補(bǔ)不足
由于數(shù)組的檢測(cè)通過重寫數(shù)組方法實(shí)現(xiàn)风宁,所以 Vue 并不能直接通過索引操作數(shù)組,也不能使用其 length 屬性操作數(shù)組蛹疯。于是 Vue 實(shí)現(xiàn)了兩個(gè) API戒财。以下兩個(gè)方法實(shí)現(xiàn)第一種功能:
1. Vue.set(vm.items, indexOfItem, newValue)或vm.$set(vm.items, indexOfItem, newValue)
2. vm.items.splice(indexOfItem, 1, newValue)
以下方法實(shí)現(xiàn)第二種功能:
1. vm.items.splice(newLength)
這兩個(gè)方法有效彌補(bǔ)了上述不足。
二捺弦、聲明響應(yīng)式property
由于 Vue 不允許動(dòng)態(tài)添加根級(jí)響應(yīng)式 property饮寞,所以你必須在初始化實(shí)例前聲明所有根級(jí)響應(yīng)式 property,哪怕只是一個(gè)空值:
var vm = new Vue({
data: {
// 聲明 message 為一個(gè)空值字符串
message: ''
},
template: '<div>{{ message }}</div>'
})
// 之后設(shè)置 `message`
vm.message = 'Hello!'
如果你未在 data 選項(xiàng)中聲明 message列吼,Vue 將警告你渲染函數(shù)正在試圖訪問不存在的 property幽崩。
三、異步更新隊(duì)列
Vue 在觀察數(shù)據(jù)變化時(shí)并不是直接更新 DOM寞钥,而是開啟一個(gè)隊(duì)列慌申,并緩存同一個(gè)事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變。在緩存時(shí)會(huì)除去重復(fù)數(shù)據(jù)理郑,從而避免不必要的計(jì)算和 DOM 操作蹄溉。然后,在下一個(gè)事件循環(huán) tick 中您炉,Vue 刷新隊(duì)列并執(zhí)行實(shí)際(已去重)的工作柒爵。
Vue 這種去重機(jī)制減少了開銷,如果一個(gè)for循環(huán)來動(dòng)態(tài)改變數(shù)據(jù) 100 次赚爵,其實(shí)它只會(huì)應(yīng)用最后一次改變棉胀,如果沒有這種機(jī)制,DOM 就要重繪 100 次冀膝。Vue會(huì)根據(jù)當(dāng)前瀏覽器環(huán)境優(yōu)先使用原生的Promise.then 和MutationObserve膏蚓。如果都不支持就會(huì)采用setTimeout代替。為了在數(shù)據(jù)變化之后等待 Vue 完成更新 DOM畸写,可以在數(shù)據(jù)變化之后立即使用 Vue.nextTick(callback)驮瞧。這樣回調(diào)函數(shù)將在 DOM 更新完成后被調(diào)用。
說人話就是 Vue 中的 Dom 更新并不是立即執(zhí)行的枯芬,在觀察數(shù)據(jù)變化后會(huì)將 Dom 更新存放起來论笔,并對(duì)其進(jìn)行一些去重工作,比如某個(gè)數(shù)據(jù)被循環(huán)了千所,不會(huì)直接循環(huán)N次狂魔,而是存起來一次性更新完以實(shí)現(xiàn)更高的性能。也就是說數(shù)據(jù)雖然已經(jīng)改變淫痰,但Dom的更新不是實(shí)時(shí)的最楷,要想針對(duì)Dom做一些操作,就得用到 Vue.nextTick(callback) 方法,在其回調(diào)函數(shù)里實(shí)現(xiàn)自己想要進(jìn)行的操作籽孙。