vue中雙向數(shù)據(jù)綁定是如何實現(xiàn)的
????數(shù)據(jù)雙向綁定,一個是監(jiān)聽頁面的數(shù)據(jù)變化晌端,一個是將數(shù)據(jù)變化映射到頁面捅暴。
分成兩個進程,一個進程是對掛載目標元素模板里的v-model和{{ }}這兩個指令進行編譯(綠色)咧纠。另一個進程是對傳進去的data對象里面的數(shù)據(jù)進行監(jiān)聽(紅色)蓬痒。
紅色:
當你把一個普通的 JavaScript 對象傳給 Vue 實例的data選項,Vue 將遍歷此對象所有的屬性漆羔,并使用Object.defineProperty把這些屬性全部加上set和get訪問器梧奢,這樣在設置data的屬性值的時候,會觸發(fā)set方法演痒,那么set方法主要有兩個作用亲轨,一是改變data里面的屬性值,二是發(fā)出數(shù)據(jù)變化的通知鸟顺。Observer作為數(shù)據(jù)的觀察者惦蚊,讓數(shù)據(jù)對象的讀寫操作都處于自己的監(jiān)管之下,Dep作為Watcher(訂閱器)的收集者讯嫂,當數(shù)據(jù)發(fā)生變化set會發(fā)出通知蹦锋,會被Observer觀察到,然后由Dep通知到Watcher欧芽,最后更新視圖莉掂。
綠色:
指令解析器Compile,對每個節(jié)點元素進行掃描和解析千扔,將相關(guān)指令對應初始化成一個訂閱者Watcher憎妙,同樣由Dep進行收集,然后由Dep通知到Watcher昏鹃,最后更新視圖尚氛。
節(jié)點介紹
數(shù)據(jù)監(jiān)聽器觀察者Observer,能夠?qū)?shù)據(jù)對象的所有屬性進行監(jiān)聽洞渤,讓數(shù)據(jù)對象的讀寫操作都處于自己的監(jiān)管之下阅嘶,當數(shù)據(jù)發(fā)生變化set會發(fā)出通知,會被Observer觀察到载迄,然后由Dep通知到Watcher讯柔,最后更新視圖。
實現(xiàn)數(shù)據(jù)的雙向綁定护昧,首先要對數(shù)據(jù)進行劫持監(jiān)聽魂迄,所以我們需要設置一個監(jiān)聽器Observer,用來監(jiān)聽所有屬性
Watcher將數(shù)據(jù)監(jiān)聽器和指令解析器連接起來惋耙,數(shù)據(jù)的屬性變動時网棍,執(zhí)行指令綁定的相應回調(diào)函數(shù),
1.如果屬性發(fā)上變化了望抽,就需要告訴訂閱者Watcher看是否需要更新冶匹。
指令解析器Compile陷虎,
對每個節(jié)點元素進行掃描和解析,將相關(guān)指令對應初始化成一個訂閱者Watcher
Dep:因為訂閱者是有很多個,所以我們需要有一個消息訂閱器Dep來專門收集這些訂閱者,然后在監(jiān)聽器Observer和訂閱者Watcher之間進行統(tǒng)一管理的
詳述虛擬DOM中的diff算法
Vue的核心是雙向綁定和虛擬DOM(下文我們簡稱為vdom)昂利,vdom是樹狀結(jié)構(gòu),其節(jié)點為vnode铁坎,vnode和瀏覽器DOM中的Node一一對應蜂奸,通過vnode的elm屬性可以訪問到對應的Node。
vdom因為是純粹的JS對象硬萍,所以操作它會很高效扩所,但是vdom的變更最終會轉(zhuǎn)換成DOM操作,為了實現(xiàn)高效的DOM操作襟铭,一套高效的虛擬DOM diff算法顯得很有必要碌奉。
vdom: 雙向綁定虛擬dom短曾。下面是vue的diff算法參考圖
need-to-insert-img
一寒砖、簡單的diff設計:
遍歷newVdom的節(jié)點,找到他在oldVdom中的位置嫉拐,找到就移動對應的dom元素哩都,沒找到說明是新增節(jié)點,則創(chuàng)建一個節(jié)點插入婉徘。遍歷完后如果oldVdom中還有沒有處理過的節(jié)點漠嵌,說明這些節(jié)點在newVdom中被刪除了,刪除它們即可盖呼。
二儒鹿、Vue的diff實現(xiàn):
1、優(yōu)先處理特殊場景
(1)几晤、頭部的同類型節(jié)點约炎、尾部的同類型節(jié)點
這類節(jié)點更新前后位置沒有發(fā)生變化,所以不用移動它們對應的DOM
(2)蟹瘾、頭尾/尾頭的同類型節(jié)點
這類節(jié)點位置很明確圾浅,不需要再花心思查找,直接移動DOM就好
處理了這些場景之后憾朴,一方面一些不需要做移動的DOM得到快速處理狸捕,另一方面待處理節(jié)點變少,縮小了后續(xù)操作的處理范圍众雷,性能也得到提升
2灸拍、“原地復用”
“原地復用”是指Vue會盡可能復用DOM做祝,盡可能不發(fā)生DOM的移動。Vue在判斷更新前后指針是否指向同一個節(jié)點鸡岗,其實不要求它們真實引用同一個DOM節(jié)點剖淀,實際上它僅判斷指向的是否是同類節(jié)點
虛擬DOM對比時,會用到diff算法
虛擬DOM什么時候會被比對纤房?
當數(shù)據(jù)發(fā)生變化的時候就會被比對
那什么時候數(shù)據(jù)會發(fā)生改變呢纵隔?
要么改變了state,要么改變了props(props的改變其實是他的父組件的state發(fā)生了改變)
setState方法炮姨,其實是異步的捌刮,為什么是異步的?實際為了提升React底層的性能舒岸,假設:調(diào)用三次setState變更三組數(shù)據(jù)绅作,大家想頁面會怎么做或者說React會怎么做?我們想的是React可能會做三次比對更新三次視圖蛾派。又假設三次更新間隔非常小俄认,這樣會耗費性能,React可以把三次合并為一次洪乍,只去做一次虛擬DOM的比對眯杏,然后更新一次視圖,這樣的話就可以省去兩次比對性能上的耗費壳澳。
同層比對岂贩,如果一致,那么繼續(xù)比對第二層巷波,如果比對一樣了萎津,繼續(xù)往下比對。
如果比對到不一樣了抹镊,React會這么做锉屈,它不會再繼續(xù)往下比對了,而是從不一樣的這一層開始直接用新的覆蓋掉就得DOM節(jié)點,這樣的話豈不是性能并未得到最大提升垮耳?這樣的話會造成重復節(jié)點的浪費颈渊,。那這樣比對會有什么好處呢氨菇?同層比對帶來的好處就是比對的算法特別簡單儡炼,雖然可能會造成DOM上的重新渲染的浪費,但是大大的減少了虛擬DOM之間比對的算法上的性能消耗查蓉,所以React中采用了同層比對的算法乌询。
遍歷時候key的問題:
假如:數(shù)組中有五條數(shù)據(jù),渲染到頁面豌研,然后生成五個虛擬DOM樹妹田,接下來我往里面增加了一條數(shù)據(jù)于是數(shù)據(jù)發(fā)生變化會生成一個新的虛擬DOM樹唬党,然后我們會做兩個虛擬DOM的比對也就是上下進行比對匹配關(guān)系,如果每一個虛擬DOM的節(jié)點沒有一個key值鬼佣,它就沒有一個自己的名字驶拱,當我們在做兩個虛擬DOM樹的比對的時候節(jié)點和節(jié)點之間的關(guān)系就很難被確立,我們得做兩層循環(huán)的比較晶衷,這樣的話比較起來就很麻煩了蓝纲,當然也是很耗費性能的。
我們可以這樣優(yōu)化晌纫,假如我們在做DOM節(jié)點的循環(huán)的時候税迷,我們可以給每個節(jié)點起個名字,A锹漱、B箭养、C、D哥牍、E在第二次循環(huán)的時候我們有六個毕泌,以前的ABCDE還存在還是叫做ABCDE,我又增加了一個節(jié)點Z進來這個時候比對就很簡單了嗅辣,我們根據(jù)他們的名字進行比對撼泛,馬上就能知道ABCDE都一致,可以繼續(xù)復用辩诞,只有Z不同坎弯,我們快速的建立關(guān)聯(lián)后把Z增加到這個DOM樹上就可以了。所以極大的提升了虛擬DOM比對的性能译暂。
如果提升性能有個前提我們盡量不要用下標,因為大家看按照下標的話右圖ABCDE撩炊,下面新的DOM樹ABCDE和上面的其實不再是對應的關(guān)系了外永,對導致key值不穩(wěn)定,key值是變化的拧咳,失去了存在的意義了伯顶。那用什么比較合適呢?唯一不變化的骆膝、穩(wěn)定的值祭衩。
大神總結(jié)
Vue的雙向數(shù)據(jù)綁定是通過數(shù)據(jù)劫持結(jié)合發(fā)布者訂閱者模式來實現(xiàn)的
個人理解:在new Vue的時候,在Observer中通過Object.defineProperty()達到數(shù)據(jù)劫持阅签,代理所有數(shù)據(jù)的getter和setter屬性掐暮,在每次觸發(fā)setter的時候,都會通過Dep來通知Watcher政钟,Watcher作為Observer數(shù)據(jù)監(jiān)聽器與Compile模板解析器之間的橋梁路克,當Observer監(jiān)聽到數(shù)據(jù)發(fā)生改變的時候樟结,通過Updater來通知Compile更新視圖
而Compile通過Watcher訂閱對應數(shù)據(jù),綁定更新函數(shù)精算,通過Dep來添加訂閱者瓢宦,達到雙向綁定
vue提供了幾種腳手架模板
vue-cli + vue2.0 + vuex + vue-router + axios + element-ui(不知道這道題指什么,所以寫了vue全家桶)
mvvm開發(fā)模式討論
1.標準MVVM(由View實例化Model)
need-to-insert-img
這種由View調(diào)用Model, 并具體由View負責Model實例化的方式是最為普遍的灰羽,非常適合于需要跨平臺的應用驮履。當然,Model并不知道View的存在廉嚼,因此View要承擔所有的界面邏輯疲吸,好在WPF已經(jīng)給出了足夠多的解決方案,觸發(fā)器前鹅,模板摘悴。基本絕大多數(shù)需求都能滿足舰绘。
2.插件結(jié)構(gòu)(Model實例化View)
這種做法的最常見場合應該是插件系統(tǒng)蹂喻。一個個的Model其實是一個個的插件,它們應該具備自治性捂寿。因此口四,應該由自身負責界面的產(chǎn)生。
?? 它的好處是可以通過Model更加精細的調(diào)節(jié)View的行為秦陋,你可以在任何時候獲得View內(nèi)部ListBox的SelectIndex, 而不用麻煩的用Binding蔓彩。 打個比方說,游戲開發(fā)中驳概,你需要隨時控制物體的運動速度和方向赤嚼,這樣Model就必須控制View. 綁定很難解決這類問題。
need-to-insert-img
?每一個功能庫都有完整的自治性顺又,當你將該功能庫拷貝到主框架之下時更卒,它就會自動加載,由Model負責View的生成稚照。一切合情合理蹂空。
3. 組裝車間(第三方組裝View和Model)
這種思路來自于工廠方法,類似于裝配車間果录,View和Model都不負責互相的實例化上枕。而有一個“管理器”負責組裝它們。這樣的好處在于可配置弱恒。你可以通過配置文件動態(tài)的改變View.
這種做法徹底的隔絕了View和Model, 同時通過配置選項辨萍,可以隨時修改View〗锉耍可謂是一種不錯的設計分瘦。 但是蘸泻,必需看到,對于View來說,Model沒有任何管理的權(quán)限嘲玫。下圖展示了它的基本邏輯:
need-to-insert-img
如果最終你依舊需要兩邊互相控制悦施,可以考慮采用dynamic關(guān)鍵字。
名稱組裝邏輯適用場合缺點備注
標準MVVMView實例化Model常用的跨平臺場合Model無法控制任何View適用于自底向上的分層設計
Model實例化ViewModel實例化View插件結(jié)構(gòu)或用于游戲開發(fā)存在一定的耦合適用于按功能劃分的插件型類庫設計去团,或要求Model大量控制View的場合
組裝車間第三方管理器實例化和組裝Model和View可動態(tài)替換所有View兩者徹底隔絕抡诞,沒有控制靈活性大型系統(tǒng)的嚴格設計
常見的幾種mvvm的實現(xiàn)方式
1.發(fā)布者-訂閱模式(backbone.js)
2.臟值檢查(angular.js)
3.數(shù)據(jù)劫持(vue.js)