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