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