雙向綁定如何實(shí)現(xiàn):
????????1互拾、我們需要一個(gè)方法來(lái)識(shí)別視圖中哪個(gè)元素被設(shè)置了雙向綁定世蔗。
????????2、我們需要監(jiān)視視圖和數(shù)據(jù)的變化珊随。
????????3述寡、我們需要將所有變化傳播到綁定視圖或者數(shù)據(jù)柿隙。
幾種實(shí)現(xiàn)數(shù)據(jù)雙向綁定的做法:1、發(fā)布者-訂閱者模式(backbone.js)鲫凶、臟值檢查(angular.js)禀崖、數(shù)據(jù)劫持(vue.js)。
發(fā)布者-訂閱者模式:
? ??????視圖驅(qū)動(dòng)數(shù)據(jù)變化螟炫,主要應(yīng)用于input波附、select、textarea等元素昼钻,通過(guò)監(jiān)聽dom的change掸屡、keypress、keyup等事件來(lái)觸發(fā)事件改變數(shù)據(jù)層數(shù)據(jù)然评。
? ??????數(shù)據(jù)驅(qū)動(dòng)視圖變化仅财,一個(gè)數(shù)組對(duì)象el存放支持雙綁的dom對(duì)象,一個(gè)儲(chǔ)存 綁定值的對(duì)象data碗淌,一個(gè)儲(chǔ)存 設(shè)置值函數(shù)的對(duì)象fun盏求,一個(gè)調(diào)用函數(shù)set(循環(huán)每個(gè)el,再循環(huán)每個(gè)el的屬性,如果某個(gè)指定的屬性存在,就調(diào)用對(duì)應(yīng)fun),一旦改變數(shù)據(jù)就調(diào)用set函數(shù)亿眠。
臟值檢查:
? ? ? ? 和上種方式類似碎罚,但在數(shù)據(jù)驅(qū)動(dòng)視圖變化,不是改完值手動(dòng)觸發(fā)set函數(shù)觸發(fā)視圖更新缕探,而是通過(guò)setInterval()定時(shí)輪詢檢測(cè)數(shù)據(jù)變化觸發(fā)set函數(shù)魂莫。
數(shù)據(jù)劫持:? ??
? ? ? ? vue.js是通過(guò)數(shù)據(jù)劫持(object.defineProperty()的set和get)結(jié)合發(fā)布者-訂閱者方式,在數(shù)據(jù)變動(dòng)時(shí)發(fā)布消息給訂閱者爹耗,觸發(fā)相應(yīng)回調(diào)監(jiān)聽耙考。
? ? ? ? 1、實(shí)現(xiàn)一個(gè)數(shù)據(jù)監(jiān)聽器Observer潭兽,能夠?qū)?shù)據(jù)對(duì)象的所有屬性進(jìn)行監(jiān)聽倦始,如有變動(dòng)可拿到最新值并通知訂閱者。
? ? ? ? 2山卦、實(shí)現(xiàn)一個(gè)指令解析器Compile鞋邑,對(duì)每個(gè)元素節(jié)點(diǎn)的指令進(jìn)行掃描和解析,根據(jù)指令模版替換數(shù)據(jù)账蓉,以及綁定相應(yīng)的更新函數(shù)枚碗。
? ? ? ? 3、實(shí)現(xiàn)一個(gè)watcher铸本,做為連接Observer和Compile的橋梁肮雨,能夠訂閱并收到每個(gè)屬性變動(dòng)的通知。執(zhí)行指令綁定的相應(yīng)回調(diào)函數(shù)箱玷,從而更新視圖怨规。
? ? ? ? 4陌宿、mvvm入口函數(shù),整合以上三者波丰。
分析
實(shí)現(xiàn)mvvm主要包括兩個(gè)方面壳坪,數(shù)據(jù)變化更新視圖,視圖變化更新數(shù)據(jù)掰烟。關(guān)鍵在于前者數(shù)據(jù)變化更新視圖爽蝴,后者可以通過(guò)事件監(jiān)聽即可。所以我們著重看前者媚赖。我們可以通過(guò)Object.defineProperty()對(duì)屬性設(shè)置一個(gè)set函數(shù)來(lái)觀察數(shù)據(jù)變化做出反應(yīng)霜瘪,我們來(lái)具體實(shí)現(xiàn)一下。
實(shí)現(xiàn)
我們需要一個(gè)觀察者Observer惧磺,以 遍歷遞歸 方式 為每個(gè)對(duì)象屬性通過(guò)Object.defineProperty()添加觀察颖对,在 獲取對(duì)象屬性getter函數(shù)中 需要在訂閱者Watcher初始化時(shí)把他裝進(jìn)訂閱器Dep,在?對(duì)象屬性值更新setter函數(shù)中 需要 改變oldVal—newVal磨隘,并通知訂閱者觸發(fā)相應(yīng)函數(shù)缤底。
我們需要一個(gè)訂閱者Watcher,它需要有初始化功能( 結(jié)合Observer的getter函數(shù)初始化時(shí)把自己添加進(jìn)Dep )番捂,需要在數(shù)據(jù)屬性值變化時(shí)个唧,調(diào)用相關(guān)函數(shù)重新賦予訂閱者新值。
我們需要一個(gè)解析者Compile设预,遍歷+遞歸 去解析dom節(jié)點(diǎn)徙歼,若解析到綁定相關(guān)字符串,就將初始化的數(shù)據(jù)初始化到視圖中鳖枕,實(shí)例化一個(gè)訂閱者并綁定更新函數(shù)魄梯。
? ? ? ? 1、實(shí)現(xiàn)一個(gè)觀察者Observer宾符,用來(lái)劫持并觀察所有屬性酿秸,如果有變動(dòng),就通知訂閱者魏烫。
? ? ? ? 2辣苏、實(shí)現(xiàn)一個(gè)訂閱者Watcher,可以收到屬性的變化并執(zhí)行相應(yīng)的函數(shù)哄褒,從而更新視圖稀蟋。
? ? ? ? 3、實(shí)現(xiàn)一個(gè)解析者compile呐赡,可以掃描和解析每個(gè)節(jié)點(diǎn)的相關(guān)指令糊治,并根據(jù)初始化模版數(shù)據(jù)去初始化相應(yīng)的訂閱器。
維護(hù)訂閱器
維護(hù)一個(gè)訂閱器罚舱,負(fù)責(zé)實(shí)例化訂閱者井辜,當(dāng)初始化和更新時(shí),調(diào)用相關(guān)函數(shù)管闷。
Dep為一個(gè)構(gòu)造函數(shù)粥脚,有subs數(shù)組儲(chǔ)存訂閱者,addSub和notify兩個(gè)函數(shù)包个,addSub負(fù)責(zé)在初始化訂閱者初始化時(shí)(當(dāng)Compile解析dom檢測(cè)到相關(guān)字符串 進(jìn)行訂閱者初始化)添加到訂閱器中刷允,notify負(fù)責(zé)在觀察到數(shù)據(jù)更新時(shí)被觸發(fā) 去調(diào)用訂閱者的更新函數(shù)。
實(shí)現(xiàn)Observe
Observer主要是對(duì) 對(duì)象屬性 通過(guò) defineProperty()進(jìn)行監(jiān)聽碧囊,getter時(shí)幫助訂閱者初始化時(shí)加入訂閱器树灶,setter時(shí)更新對(duì)象屬性及通知訂閱者調(diào)用函數(shù)更新訂閱者值。
一個(gè)構(gòu)造函數(shù)Observer糯而,一個(gè)觸發(fā)函數(shù)observe天通。
observe函數(shù)判斷參數(shù)是否是對(duì)象,是的話實(shí)例化一個(gè)Observe對(duì)象(對(duì)象屬性 遍歷遞歸 時(shí)判斷子屬性值 是否是對(duì)象)熄驼。
Observe接受一個(gè)對(duì)象像寒,有Walk、defineReactive兩個(gè)函數(shù)瓜贾,Walk負(fù)責(zé)遍歷對(duì)象每個(gè)屬性調(diào)用defineReactive诺祸。defineReactive負(fù)責(zé)遞歸每個(gè)對(duì)象屬性 設(shè)置監(jiān)聽器。
Q/A
每次defineReactive都會(huì)new Dep()再在getter中初始化push訂閱者祭芦,Dep中怎么會(huì)有所有訂閱者筷笨?
其實(shí)只有一個(gè)訂閱者,每次都會(huì)實(shí)例一個(gè)Dep龟劲,有多個(gè)Dep胃夏。
setter時(shí)循環(huán)訂閱器中每個(gè)訂閱者調(diào)用update函數(shù),update函數(shù)做了什么事情咸灿?
監(jiān)聽器更新數(shù)據(jù)時(shí)觸發(fā)的更新函數(shù)判斷 new/old數(shù)據(jù)是否相同构订,不相同就把舊Value賦予新值,并在全局執(zhí)行回調(diào)函數(shù)(傳入新舊值)
實(shí)現(xiàn)訂閱者
????????每個(gè)訂閱者實(shí)例有4個(gè)對(duì)象屬性避矢,cb(監(jiān)聽器更新數(shù)據(jù)時(shí)觸發(fā)的函數(shù))悼瘾,vm(組件對(duì)象),exp(綁定的屬性key)审胸,value(綁定的屬性值)亥宿。run和get兩個(gè)函數(shù),run為監(jiān)聽器更新數(shù)據(jù)時(shí)觸發(fā)的更新函數(shù)判斷 new/old數(shù)據(jù)是否相同砂沛,不相同就把舊Value賦予新值烫扼,并在全局執(zhí)行回調(diào)函數(shù)(傳入新舊值)。get為初始化時(shí)把自己添加進(jìn)訂閱器Dep()中碍庵。
實(shí)現(xiàn)Compile
解析器主要作用是 遍歷遞歸解析dom節(jié)點(diǎn)映企,解析到雙向綁定的指令悟狱,將初始化的數(shù)據(jù)初始化到視圖中,實(shí)例化訂閱器并綁定更新函數(shù)堰氓。
????????Compile構(gòu)造函數(shù)有3個(gè)屬性挤渐,vm(全局環(huán)境),el(html最高節(jié)點(diǎn))双絮,fragment用來(lái)存放dom節(jié)點(diǎn)(我們數(shù)據(jù)更新dom時(shí)需要多次操作dom浴麻,通過(guò)createDocumentFragment創(chuàng)建一個(gè)虛擬父節(jié)點(diǎn)fragment,把dom移入fragment進(jìn)行操作囤攀,操作完了直接替換整個(gè)dom(一次性替換操作效率更高比一次次操作塊70%)软免。
init()調(diào)用了nodeToFragment、compileElement焚挠、compile三個(gè)函數(shù)膏萧。
nodeToFragment,把dom塞入fragment虛擬父節(jié)點(diǎn)宣蔚。
compileElement向抢,遍歷遞歸fragment中dom,判斷是元素節(jié)點(diǎn)的話執(zhí)行compile函數(shù)胚委,是文本節(jié)點(diǎn)且有'{{}}'的話執(zhí)行compileText函數(shù)挟鸠。如果節(jié)點(diǎn)有子節(jié)點(diǎn)繼續(xù)遞歸執(zhí)行compileElement。
compile亩冬,對(duì)dom節(jié)點(diǎn)的屬性節(jié)點(diǎn)進(jìn)行遍歷艘希,若有"v-"相關(guān)字段屬性name,若有":on"相關(guān)字段則綁定的是事件硅急,執(zhí)行compileEvent事件覆享,否則執(zhí)行compileMdole事件。
"{{}}"對(duì)應(yīng)的compileText函數(shù)营袜,負(fù)責(zé)初始化節(jié)點(diǎn)textContent數(shù)據(jù)撒顿,并新增一個(gè)訂閱者。
"v-on:"對(duì)應(yīng)的compileEvent函數(shù)荚板,負(fù)責(zé)取得事件名和事件值 通過(guò)addEventListener監(jiān)聽函數(shù)觸發(fā)執(zhí)行對(duì)應(yīng)事件凤壁。
"v-model"對(duì)應(yīng)compileModel函數(shù),負(fù)責(zé)初始化節(jié)點(diǎn)value數(shù)據(jù)跪另,并新增一個(gè)訂閱者拧抖,再通過(guò)對(duì)node.addEventListener('input', function(...))在input數(shù)據(jù)變化時(shí)實(shí)時(shí)改變對(duì)象數(shù)據(jù)
mvvm入口
我們把整個(gè)流程結(jié)合起來(lái)看一遍
? ? ? ? 入口構(gòu)造函數(shù),需要一個(gè)數(shù)據(jù)對(duì)象data免绿,需要一個(gè)函數(shù)對(duì)象methods(當(dāng)data中數(shù)據(jù)變化時(shí)調(diào)用)唧席。
????????有一個(gè)proxyKeys函數(shù),作用,在訪問(wèn)selfVue的屬性時(shí)代理為selfVue.data屬性(this.data.name = 'canfoo'我們可以用更簡(jiǎn)潔的方式 this.name = 'canfoo'?)淌哟,也是通過(guò)遍歷每個(gè)data屬性為每個(gè)屬性添加監(jiān)聽器object.defineProperty()迹卢,在get內(nèi)把對(duì)this.key的訪問(wèn)替換成this.data.key的屬性值來(lái)處理。
? ? ? ? 監(jiān)聽器observe對(duì)數(shù)據(jù)對(duì)象進(jìn)行監(jiān)聽绞绒。
????????實(shí)例化compile對(duì)象婶希,把節(jié)點(diǎn)傳入,在compile會(huì)對(duì)dom節(jié)點(diǎn)進(jìn)行遍歷遞歸蓬衡,處理3種情況。1彤枢、"{{}}",初始化節(jié)點(diǎn)texteContent數(shù)值狰晚,實(shí)例化一個(gè)訂閱者。2缴啡、"v-model",初始化節(jié)點(diǎn)value數(shù)值壁晒,實(shí)例化一個(gè)訂閱者,并監(jiān)聽input事件實(shí)時(shí)對(duì)數(shù)據(jù)更新业栅。3秒咐、"v-on:"把對(duì)應(yīng)事件名和methods中事件進(jìn)行綁定監(jiān)聽addEventListener。
????????實(shí)例化訂閱者Watcher碘裕,會(huì)在初始化時(shí)把自己添加進(jìn)訂閱器Dep()携取,在數(shù)據(jù)更新時(shí)會(huì)通過(guò)this.c.call()觸發(fā)傳進(jìn)來(lái)的函數(shù) 處理數(shù)據(jù)。
? ? ? ? 所有事情處理好后執(zhí)行mounted函數(shù)帮孔。
二次理解:
我們定義好基本數(shù)據(jù)
會(huì)有一個(gè)入口
????????入口及之后做的事情:1雷滋、取到定義好的數(shù)據(jù)。2文兢、把對(duì)this.xxx的訪問(wèn)代理到this.data.xxx(寫法更簡(jiǎn)潔)晤斩。3、我們需要一個(gè)訂閱器Dep姆坚,來(lái)收集訂閱者Watcher澳泵,在觀察器檢測(cè)到數(shù)據(jù)變化時(shí),調(diào)用訂閱者Watcher的相關(guān)處理函數(shù)兼呵。4兔辅、執(zhí)行觀察者(Observe)遍歷遞歸 data屬性,通過(guò)訪問(wèn)器屬性O(shè)bject.defineProperty() 給他們都綁定觀察器萍程。5幢妄、執(zhí)行編譯者(Compile)分析編譯Dom結(jié)構(gòu),檢測(cè)到 相應(yīng)字符串 實(shí)例化一個(gè)訂閱者(Watcher)茫负,實(shí)例化時(shí)它會(huì)主動(dòng)觸發(fā)觀察者的getter函數(shù)把自己push進(jìn)訂閱器Dep蕉鸳。
首次數(shù)據(jù)賦值在什么時(shí)候?
????????首次數(shù)據(jù)賦值在解析者Compile解析到相關(guān)字符串時(shí) 實(shí)例化訂閱者Watcher的同時(shí)進(jìn)行了賦值。
之后數(shù)據(jù)更新 執(zhí)行流程如何潮尝?
????????觀察者Observe會(huì)在數(shù)據(jù)更新時(shí)觀察到榕吼,會(huì)執(zhí)行setter函數(shù)調(diào)用dep中notify函數(shù),notify函數(shù)再調(diào)用訂閱者的Update函數(shù)去調(diào)用相關(guān)方法處理勉失。
訂閱器Dep存在的意義羹蚣?
????????不知道?乱凿?顽素??? 每個(gè)Observer.prototype.defineReactive都會(huì)new Dep徒蟆。相當(dāng)于每個(gè)Watcher訂閱者都有一個(gè)Dep()胁出。