摘要:訂閱者的實(shí)現(xiàn)如下將自己添加到訂閱器的操作緩存自己強(qiáng)行執(zhí)行監(jiān)聽器里的函數(shù)釋放自己到此為止,簡(jiǎn)單版的設(shè)計(jì)完畢,這時(shí)候我們需要將和關(guān)聯(lián)起來(lái),就可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的雙向數(shù)據(jù)綁定了。同樣使用數(shù)據(jù)劫持。。
什么是雙向綁定
簡(jiǎn)單說(shuō)就是在數(shù)據(jù)和UI之間建立雙向的通信通道,當(dāng)用戶通過(guò)Function改變了數(shù)據(jù),那么這個(gè)改變也會(huì)立即反映到UI上;或者說(shuō)用戶通過(guò)UI的操作也會(huì)隨之引起對(duì)應(yīng)的數(shù)據(jù)變動(dòng)。
Vue是如何實(shí)現(xiàn)雙向數(shù)據(jù)綁定的?數(shù)據(jù)劫持?什么意思呢?太籠統(tǒng)了。與其說(shuō)是數(shù)據(jù)劫持,更應(yīng)該說(shuō)是對(duì)象數(shù)據(jù)對(duì)象的setter和Getter實(shí)現(xiàn)劫持。但是Object.defineProperty僅僅是實(shí)現(xiàn)了對(duì)數(shù)據(jù)的監(jiān)控,后續(xù)實(shí)現(xiàn)對(duì)UI的重新渲染并不是它做的,所以這里還涉及到發(fā)布-訂閱模式;過(guò)程是,當(dāng)監(jiān)控的數(shù)據(jù)對(duì)象被更改后,這個(gè)變更會(huì)被廣播給所有訂閱該數(shù)據(jù)的watcher,然后由該watcher實(shí)現(xiàn)對(duì)頁(yè)面的重新渲染。
步驟:
首先要對(duì)數(shù)據(jù)進(jìn)行劫持,所以我們需要設(shè)置一個(gè)監(jiān)聽器Observer,用來(lái)監(jiān)聽所有的屬性。 如果屬性發(fā)生變化,就需要告訴訂閱者Watcher看是否需要更新。 訂閱者是有很多個(gè),所以我們需要有一個(gè)消息訂閱器Dep來(lái)專門收集這些訂閱者,然后在監(jiān)聽器Observer和訂閱者Watcher之間進(jìn)行統(tǒng)一管理的。 還需要一個(gè)指令解析器Compile,對(duì)每個(gè)節(jié)點(diǎn)元素進(jìn)行掃描和解析,將相關(guān)指令對(duì)應(yīng)初始化成一個(gè)訂閱者Watcher,并替換模板數(shù)據(jù)或者綁定相應(yīng)的函數(shù),此時(shí)當(dāng)訂閱者Watcher接收到相應(yīng)屬性的變化,就會(huì)執(zhí)行相應(yīng)的更新函數(shù),從而更新視圖。 1.實(shí)現(xiàn)一個(gè)監(jiān)聽器Observer,用來(lái)劫持并監(jiān)聽所有屬性,如果有變動(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)的訂閱器。
流程圖如下:
Observer是一個(gè)數(shù)據(jù)監(jiān)聽器,其實(shí)現(xiàn)核心方法就是Object.defineProperty().如果要對(duì)所有屬性都進(jìn)行監(jiān)聽的話,那么可以通過(guò)遞歸方法遍歷所有屬性值,并對(duì)其進(jìn)行Object.defineProperty()處理。
function defineReactive(data,key.val){ observe(val);//遞歸遍歷所有子屬性 Obejct.defineProperty(data,key,{ enumerable:true, configurable:true, get:function(){ return val; }, set:function(newVal){ val = newVal; console.log("屬性"+key+"已經(jīng)被監(jiān)聽了") } }) } function observe(data){ if(!data || typeof data !=="object"){ return; } Object.keys(data).forEach(function(key){ defineReactive(data,key,data[key]) }) } var library={ book1:{ name:"" }, book2:"" } observe(library); library.book1.name="123"; library.book2="456"
思路分析中,需要?jiǎng)?chuàng)建一個(gè)可以容納訂閱者的消息訂閱器Dep,訂閱器Dep主要負(fù)責(zé)收集訂閱者,然后再屬性變化的時(shí)候執(zhí)行對(duì)應(yīng)訂閱者的更新函數(shù)。所以顯然訂閱者需要有一個(gè)容器,這個(gè)容器就是list,將上面的Observer稍微改造下,植入消息訂閱者:
function defineReactive(data,key,val){ observe(val)//遞歸遍歷所有子屬性 var dep = new Dep(); Object.defineProperty(data,key,{ enumerable:true, configurable:true, get:function(){ if(是否需要添加訂閱者){ dep.addSub(watcher);//在這里添加一個(gè)訂閱者 } return val; }, set:function(newVal){ if(val === newVal){ return; } val = newVal; console.log("屬性"+key+"已經(jīng)被監(jiān)聽了"); dep.notify();//如果數(shù)據(jù)變化,通知訂閱者 } }) } function Dep(){ this.subs = []; } Dep.prototype={ addSub:function(sub){ this.subs.push(sub); }, notify:function(){ this.subs.forEach(function(sub){ sub.update(); }) } }
從代碼上看,我們將訂閱器Dep添加一個(gè)訂閱者設(shè)計(jì)在getter里面,這是為了讓W(xué)atcher初始化進(jìn)行觸發(fā),因此需要判斷是否需要添加訂閱者。在setter函數(shù)里面,如果數(shù)據(jù)變化,就會(huì)去通知所有訂閱者,訂閱者們就會(huì)去執(zhí)行對(duì)應(yīng)的更新函數(shù)。到此,一個(gè)比較完整的Obsever已經(jīng)實(shí)現(xiàn)了,接下來(lái)我們開始設(shè)計(jì)Watcher。
實(shí)現(xiàn)Watcher訂閱者Watcher在初始化的時(shí)候需要將自己添加進(jìn)訂閱器Dep中,那該如何添加呢?我們已經(jīng)知道監(jiān)聽器Observer是在get函數(shù)執(zhí)行了添加訂閱者Watcher的操作的,所以我們只要在訂閱者Watcher初始化的時(shí)候觸發(fā)對(duì)應(yīng)的get函數(shù)去執(zhí)行添加訂閱者操作即可,那要如何觸發(fā)get的函數(shù)呢?只要獲取對(duì)應(yīng)的屬性值就可以觸發(fā)了,原因就是我們使用了Obejct.defineProperty()進(jìn)行數(shù)據(jù)監(jiān)聽。這里還有一個(gè)細(xì)節(jié)點(diǎn)需要處理,我們只要在訂閱者Watcher初始化的時(shí)候才需要添加訂閱者,所以需要做一個(gè)判斷操作,因此可以在訂閱器上做一下手腳:在Dep.target上緩存下訂閱者,添加成功后再將其去掉就可以了。訂閱者Watcher的實(shí)現(xiàn)如下:
function Watcher(vm,exp,cb){ this.cb = cb; this.vm = vm; this.exp = exp; this.value = this.get();//將自己添加到訂閱器的操作 } Watcher.prototype={ update:function(){ this.run(); }, run:function(){ var value = this.vm.data[this.exp]; var oldVal = this.value; if(value!==oldVal){ this.value = value; this.cb.call(this.vm,value,oldVal) } }, get:function(){ Dep.target = this;//緩存自己 var value = this.vm.data[this.exp]//強(qiáng)行執(zhí)行監(jiān)聽器里的get函數(shù) Dep.target = null;//釋放自己 return value; } }
到此為止,簡(jiǎn)單版的Watcher設(shè)計(jì)完畢,這時(shí)候我們需要將Observer和Watcher關(guān)聯(lián)起來(lái),就可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的雙向數(shù)據(jù)綁定了。
簡(jiǎn)單的例子:
3.實(shí)現(xiàn)CompileDocument {{name}}
雖然上面已經(jīng)實(shí)現(xiàn)了一個(gè)雙向數(shù)據(jù)綁定的例子,但是整個(gè)過(guò)程都沒(méi)有去解析dom節(jié)點(diǎn),而是直接固定某個(gè)節(jié)點(diǎn)進(jìn)行替換數(shù)據(jù)的,所以接下來(lái)需要實(shí)現(xiàn)一個(gè)解析器Compile來(lái)做解析和綁定工作。解析器Compile實(shí)現(xiàn)步驟:
1.解析模板指令,并替換模板數(shù)據(jù),初始化視圖。
2.將模板指令對(duì)應(yīng)的節(jié)點(diǎn)綁定對(duì)應(yīng)的更新函數(shù),初始化相應(yīng)的訂閱器
為了解析模板,首先需要獲取dom元素,然后對(duì)含有dom元素上含有指令的節(jié)點(diǎn)進(jìn)行處理,因此這個(gè)環(huán)節(jié)需要對(duì)dom操作比較頻繁,所以可以先建一個(gè)fragment片段,將需要解析的dom節(jié)點(diǎn)存入fragment片段里再進(jìn)行處理:
function nodeToFragment(el){ var fragment = document.createDocumentFragment(); var child = el.firstChild; while(child){ //將Dom元素移入fragment中 fragment.appendChild(child); child = el.firstChild } return fragment; }
接下來(lái)需要遍歷各個(gè)節(jié)點(diǎn),對(duì)含有相關(guān)指定的節(jié)點(diǎn)進(jìn)行特殊處理,這里先處理最簡(jiǎn)單的情況,只對(duì)帶有{{變量}}這種形式的指令進(jìn)行處理。
function compileElement(el){ var childNodes = el.childNodes; var self = this; [].slice.call(childNodes).forEach(function(node){ var reg = /{(.*)}}/; var text = node.textContent; if(self.isTextNode(node)&®.test(text)){ self.compileText(node,reg.exec(text)[1]) } if(node.childNodes&&node.childNodes.length){ self.compileElement(node);//繼續(xù)遞歸遍歷子節(jié)點(diǎn)。 } }) } function compileText(node,exp){ var self = this; var initText = this.vm[exp]; this.updateText(node,initText); new Watcher(this.vm,exp,function(value){ self.updateText(node,value); }) } function (node,value){ node.tetxContent = typeof value =="undefined"?"":value; }
獲取到最外層節(jié)點(diǎn)后,調(diào)用compileElement函數(shù),對(duì)所有子節(jié)點(diǎn)進(jìn)行判斷,如果節(jié)點(diǎn)是文本節(jié)點(diǎn)且匹配{{}}這種形式指令的節(jié)點(diǎn)就開始進(jìn)行變異處理,編譯處理首先需要初始化視圖數(shù)據(jù),對(duì)應(yīng)上面所說(shuō)的步驟1,接下來(lái)需要生成一個(gè)并綁定更新函數(shù)的訂閱器,對(duì)應(yīng)上面所說(shuō)的步驟2.這樣就完成指令的解析、初始化、編譯三個(gè)過(guò)程,一個(gè)解析器Compile也就可以正常的工作了。為了將解析器Compile與監(jiān)聽器Obsever和訂閱者Watcher關(guān)聯(lián)起來(lái),我們需要再修改一下類SelfVue函數(shù):
function SelfVue(options){ var self = this; this.vm = this; this.data = options; Object.keys(this.data).forEach(function(key){ self.proxyKeys(key); }) observe(this.data); new Compile(options,this.vm); return this; }
createDocumentFragment:
創(chuàng)建一個(gè)新的空白的文檔片段。
語(yǔ)法:
let fragment = document.createDocumentFragment();
fragment是一個(gè)指向空DocumentFragment對(duì)象的引用。DocumentFragment是DOM節(jié)點(diǎn)。它們不是主dom數(shù)的一部分。通常的用例是創(chuàng)建文檔片段,將元素附加到文檔片段,然后將文檔片段附加到DOM樹。在DOM樹中,文檔片段被其所有的子元素所代替。
因?yàn)槲臋n片段存在于內(nèi)存中,并不在DOM樹中,所以將子元素插入到文檔片段時(shí)不會(huì)引起頁(yè)面回流。因此使用文檔片段通常會(huì)帶來(lái)更好的性能。
Document {{title}}
{{name}}
代碼分析:
1.new SelfVue() 做了三件事:1.使用Object.definePrototype代理讓訪問(wèn)selfVue的屬性代理為訪問(wèn)selfVue.data的屬性。2.同樣使用Object.definePrototype數(shù)據(jù)劫持。3.new Compile()。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/103006.html
摘要:的數(shù)據(jù)劫持版本內(nèi)部使用了來(lái)實(shí)現(xiàn)數(shù)據(jù)與視圖的雙向綁定,體現(xiàn)在對(duì)數(shù)據(jù)的讀寫處理過(guò)程中。這樣就形成了數(shù)據(jù)的雙向綁定。 MVVM由以下三個(gè)內(nèi)容組成 View:視圖模板 Model:數(shù)據(jù)模型 ViewModel:作為橋梁負(fù)責(zé)溝通View和Model,自動(dòng)渲染模板 在JQuery時(shí)期,如果需要刷新UI時(shí),需要先取到對(duì)應(yīng)的DOM再更新UI,這樣數(shù)據(jù)和業(yè)務(wù)的邏輯就和頁(yè)面有強(qiáng)耦合。 在MVVM中,U...
摘要:雙向數(shù)據(jù)綁定簡(jiǎn)言之?dāng)?shù)據(jù)動(dòng)頁(yè)面動(dòng),頁(yè)面動(dòng),數(shù)據(jù)動(dòng)典型的應(yīng)用就是在做表單時(shí)候,輸入框的內(nèi)容改動(dòng)后,跟該輸入框的的值改動(dòng)??垂倬W(wǎng)上的這個(gè)的演示案例雙向數(shù)據(jù)綁定的好處要說(shuō)出這個(gè)好處的時(shí)候,也只有在實(shí)際場(chǎng)景中才能對(duì)應(yīng)的顯示出來(lái)。 前言:本系列學(xué)習(xí)筆記從以下幾個(gè)點(diǎn)展開 什么是雙向數(shù)據(jù)綁定 雙向數(shù)據(jù)綁定的好處 怎么實(shí)現(xiàn)雙向數(shù)據(jù)綁定 實(shí)現(xiàn)雙向數(shù)據(jù)數(shù)據(jù)綁定需要哪些知識(shí)點(diǎn) 數(shù)據(jù)劫持 發(fā)布訂閱模式 ...
摘要:菜鳥教程這是一個(gè)屬性其值是字符串菜鳥教程同上這是一個(gè)屬性其值是字符串用于定義的函數(shù),可以通過(guò)來(lái)返回函數(shù)值。它們都有前綴,以便與用戶定義的屬性區(qū)分開來(lái)。 開篇語(yǔ) 我最近學(xué)習(xí)了js,取得進(jìn)步,現(xiàn)在學(xué)習(xí)vue.js.建議新手學(xué)習(xí),請(qǐng)不要用npm的方式(vue-cli,vue腳手架),太復(fù)雜了. 請(qǐng)直接下載vue.js文件本地引入,就上手學(xué)習(xí)吧參照菜鳥教程網(wǎng)站的vue.js教程http://...
摘要:而在頁(yè)面中,在之內(nèi)的元素只需寫一個(gè)。但是元素的內(nèi)容被更改之后,控件中的內(nèi)容并不會(huì)同步更新。下面的代碼,在中遍歷實(shí)例中屬性里的每一項(xiàng),并將每個(gè)與綁定。而在定義組件的代碼中,接收傳入的,并在元素中顯示中的字符串。 URL:Introduction - Vue.js 注意 所演示的示例,都是在JS中將Vue實(shí)例綁定至HTML中的指定元素,然后再通過(guò)Vue實(shí)例中data內(nèi)的屬性或者method...
摘要:而在頁(yè)面中,在之內(nèi)的元素只需寫一個(gè)。但是元素的內(nèi)容被更改之后,控件中的內(nèi)容并不會(huì)同步更新。下面的代碼,在中遍歷實(shí)例中屬性里的每一項(xiàng),并將每個(gè)與綁定。而在定義組件的代碼中,接收傳入的,并在元素中顯示中的字符串。 URL:Introduction - Vue.js 注意 所演示的示例,都是在JS中將Vue實(shí)例綁定至HTML中的指定元素,然后再通過(guò)Vue實(shí)例中data內(nèi)的屬性或者method...
閱讀 3326·2023-04-26 00:58
閱讀 1277·2021-09-22 16:04
閱讀 3323·2021-09-02 15:11
閱讀 1568·2019-08-30 15:55
閱讀 2348·2019-08-30 15:55
閱讀 3278·2019-08-23 18:41
閱讀 3470·2019-08-23 18:18
閱讀 2760·2019-08-23 17:53