在上篇文章當(dāng)中,我們實(shí)現(xiàn)了單向數(shù)據(jù)綁定澜躺,那么接下來蝉稳,咱們一步一步來實(shí)現(xiàn)雙向數(shù)據(jù)綁定。
首先最大的問題就是我們?nèi)绾稳ブ纃ata里面的數(shù)據(jù)變化了苗踪,在Vue的實(shí)例化對象的data屬性當(dāng)中颠区,可以查看到該屬性上存在著一個(gè)get和set方法,而這兩個(gè)方法實(shí)際上是通過Object.defineProperty()來實(shí)現(xiàn)數(shù)據(jù)劫持的通铲。
先來實(shí)現(xiàn)一個(gè)簡單的需求,創(chuàng)建一個(gè)簡單的對象器贩。
var Book={
? ? name:"明朝那些事"
}
但是現(xiàn)在我想要在獲取到Book.name的時(shí)候有個(gè)書名號包裹著該怎么辦呢颅夺?這就需要用到上文說到的Object.defineProperty()了。
var Book={}吧黄;
var value="";
Object.defineProperty(Book,"name",{?
? ??get:function(){
? ??????return value;
? ??},
? ??set:function(newValue){
? ??????value="《"+newValue+"》";
? ??}
});
通過Object.defineProperty( )設(shè)置了對象Book的name屬性部服,對其get和set進(jìn)行重寫操作,get就是在讀取name屬性這個(gè)值觸發(fā)的函數(shù)拗慨,set就是在設(shè)置name屬性這個(gè)值觸發(fā)的函數(shù)廓八。而在控制臺輸出的Book對象,乍一看赵抢,和Vue的數(shù)據(jù)長得有點(diǎn)類似剧蹂,說明Vue確實(shí)是通過這種方法來實(shí)現(xiàn)數(shù)據(jù)的劫持的,那么我們就可以實(shí)現(xiàn)一個(gè)監(jiān)聽器observer了烦却。
/*
* Description:監(jiān)聽器
* obj:監(jiān)聽的對象
*/
function observer(obj){
????if(!obj || typeof obj != "object"){
? ?????? return;
? ??}
? ??for(var key in obj){
? ?????? defineReactive(obj,key,obj[key]);
? ??}
}
function defineReactive(obj,key,value){
? ??//遞歸調(diào)用宠叼,監(jiān)聽該對象下面所有子對象的屬性
? ??observer(value);
? ??Object.defineProperty(obj,key,{
? ??????get:function(){
? ??????????return value;
? ??????},
? ??????set:function(newValue){
? ??????????value=newValue;
? ? ? ? }
? ??});
}
有了observer方法,我們就能夠去實(shí)現(xiàn)監(jiān)聽Vue實(shí)例化對象的data屬性值是否發(fā)生了更改其爵,也就是說在Vue類當(dāng)中調(diào)用observer方法即可冒冬,但是現(xiàn)在只是知道了哪個(gè)屬性發(fā)生了變化,卻無法實(shí)時(shí)的更新摩渺,要想實(shí)現(xiàn)這一步简烤,我們先來考慮一個(gè)問題,當(dāng)某一個(gè)屬性值發(fā)生變化的時(shí)候摇幻,是需要將所有綁定該屬性值的節(jié)點(diǎn)的值改為該屬性值乐埠,所以這就用到了發(fā)布和訂閱模式,也就是說囚企,綁定同一個(gè)屬性的每一個(gè)訂閱者都存放在同一個(gè)訂閱器內(nèi)丈咐,如果該屬性值變化了,訂閱器會(huì)逐個(gè)的發(fā)布告知每一個(gè)訂閱者龙宏。(更多詳細(xì)信息請自行百度或參考lk儒家博客)
根據(jù)理解棵逊,很容易的清楚的知道需要一個(gè)訂閱者容器類,該類的實(shí)例化對象必須含有一個(gè)數(shù)組银酗,然后有一個(gè)向該數(shù)組存放訂閱者的方法辆影,以及循環(huán)該數(shù)組通知所有訂閱者更新的方法。
//負(fù)責(zé)收集訂閱者的容器
function Dep(){
? ??this.subs=[];
}
Dep.prototype={
? ??//添加訂閱者的方法
? ??addSub:function(sub){
? ??????this.subs.push(sub);
? ??},
? ??//通知訂閱者的方法
? ??notify:function(){
? ??????this.subs.forEach(function(sub){
? ??????????//調(diào)用訂閱者的update方法讓其進(jìn)行更新
? ??????????sub.update();
? ??????});
? ??}
}
而對于訂閱者黍特,上文也進(jìn)行分析了蛙讥,也很清楚的知道每一個(gè)訂閱者的實(shí)例上都存在著一個(gè)update方法,即更新其內(nèi)容灭衷,而再仔細(xì)想一想次慢,這些訂閱者到底是什么,更新的是什么內(nèi)容呢?是不是每一個(gè)綁定Vue的屬性的節(jié)點(diǎn)都是一個(gè)訂閱者迫像,改變的是該節(jié)點(diǎn)的內(nèi)容劈愚,也就是說,這些訂閱者是在上篇文章當(dāng)中compile的時(shí)候去創(chuàng)建闻妓,需要的參數(shù)值是Vue的實(shí)例化對象菌羽,綁定的該節(jié)點(diǎn)以及綁定的Vue的屬性值。
function Watcher(vm,node,name){
? ??this.vm=vm;
? ??this.node=node;
? ??this.name=name;
? ??//初始化
? ??this.update();
}
Watcher.prototype={
? ??//更新的方法由缆,需要節(jié)點(diǎn)node,vue的實(shí)例化對象,屬性名稱
? ??update:function(){
? ??????if(this.node.nodeType==1 && this.node.nodeName === 'INPUT'){
? ??????????this.node.value=this.vm.data[this.name];
? ??????????return;
? ? ? ? }
? ??????this.node.nodeValue=this.vm.data[this.name];
? ? }
}
而現(xiàn)在我們剩下的問題就是observer和Watcher并沒有關(guān)聯(lián)到一起注祖,細(xì)心的小伙伴可能已經(jīng)發(fā)現(xiàn)了在Watcher類當(dāng)中進(jìn)行了一句初始化操作,而執(zhí)行update方法為什么屬于初始化呢均唉?可以發(fā)現(xiàn)是晨,在update方法當(dāng)中是將this.vm.data[this.name]賦值給節(jié)點(diǎn)內(nèi)容,而this.vm.data[this.name]早已經(jīng)被監(jiān)聽了浸卦,即其會(huì)在賦值之前先執(zhí)行該Vue實(shí)例化對象data屬性的get方法署鸡,所以向訂閱者容器當(dāng)中添加訂閱者的操作就是在監(jiān)聽的get方法當(dāng)中,那么又該如何區(qū)分是否是需要添加的呢限嫌,這里可以在Watcher類當(dāng)中添加一個(gè)標(biāo)識靴庆,例如Dep.target,因?yàn)檫@個(gè)是全局的怒医,所以在每一次操作完后就要對該值釋放炉抒。
到此為止,整個(gè)Vue的雙向綁定原理基本上說完了稚叹,但是運(yùn)行發(fā)現(xiàn)焰薄,當(dāng)修改輸入框內(nèi)容的時(shí)候,綁定同樣屬性值的內(nèi)容根本就沒有變化扒袖,這是由于沒有給輸入框綁定keyup事件塞茅,在該事件當(dāng)中,只需要將輸入框的內(nèi)容賦值給Vue實(shí)例化對象的data中的綁定屬性即可季率。
最后野瘦,奉上一張邏輯圖,加深理解飒泻,biubiu鞭光!