MVVM響應式實現原理:
1.模板編譯
2.數據劫持
3.watcher
MVVM------------------視圖-----模型----視圖模型
三者與Vue的對應:view對應template,vm對應newVue({…}),model對應data
文中應用到的數據名詞:
nodeType判斷節點是否是元素節點
querySelector創建一個元素節點
createDocumentFragment文檔碎片
attributes獲取元素屬性集合
textContent獲取文本內容
reduce(prev,next,currentIndex){}一個可以用上一個元素和當前元素做處理的方法
defineProperty(obj,key,value){}數據攔截的主要方法

首先建立一個vue的實例,建立mvvm.js,構建mvvm類。獲取el的節點和data放入實例中,在將Observer.js(數據劫持)和Compile.js(模板編譯)放入mvvm的js,全部在index頁面運行.
第一步:模板編譯
我首先制作Compile.js,也就是模板編譯。
首先需要獲取el 這個屬性的值 用nodeType === 1判斷是不是元素節點. 如果不是則用 queryselector() 生成一個節點 。 這樣做的目的是,有些人el:#app 有些人是document.getElementById('app')。 不管倆者如何,我們都要生成一個節點來供后續使用。
隨后判斷el節點是否存在,如果存在。則進行編譯 , 這里編譯最好不要在dom里進行遍歷編譯,非常耗性能 。 我推薦的是用 createDocumentFragment() 方法. 建立一個虛擬節點對象, 在這個虛擬節點對象里進行遍歷以及對應的操作。
那么說到虛擬節點, 我們需要將我們獲取的el節點整個放入進去 ,進行遍歷,將app里的每一個子節點都搬到fragement 變量中。
然后進行節點的編譯。這里的節點又分為元素節點和文本節點。 還是用剛剛的nodeType判斷區分嗎,然后做對應的操作。
接下來我們先編譯元素節點首先我們需要知道,獲取元素節點要做什么,為什么獲取元素節點。我是希望通過獲取元素節點上的關於vue的指令,比如:v-model,v-html,v-for。等等...那么這些指令是放在元素節點上的屬性里,所以我們用attributes獲取元素節點的屬性名的集合,也就是我們說的v-model。通過遍歷這個attr屬性名的集合,獲取每個屬性名。通過isDirective函數判斷attrName包含v-的屬性,這里我做給假設,好方便理解。這里通過上面的過濾,可以得出attrName是一個指令名的集合。那我假設這個指令名為v-model。我首先獲取v-model的值,也就是expr。然后做一個解耦對象CompileUtil,方便后面制作其他的指令。所以這里調用的是CompileUtil[model]{node,this.v,,expr};
調用model的指令后,在model這個函數里做相對應的處理。這里的watcher構造函數先不用管,后面的事情。這里的uptate['modelUptate']和model一樣放在CompileUtil中,方便管理。如果updateFn存在的話,則執行updateFn(),將v-model的值賦予input節點的value.下圖中的getVal是防止v-model=’messge.a'這種嵌套對象的。這個函數里,首先利用split將messge.a拆分成[messge,a]數組。然后利用reduce方法返回上一個元素[當前元素],而最下面的vm.$data是reduce方法遍歷的初始值。也就是data。
因為data:{messge:{a:'hello.world'}}.這樣的編譯,元素節點就可以編譯出來了,可以將data的值編譯到元素節點上了。
接下來編譯文本節點,那文本節點,我們首先獲取文本節點里的值,然后利用正則的test找{{a}},和之前的元素節點一樣,執行對應的函數。,執行對應的行數。這里第86-90可以先不管,不過這里的textVal和上面的getVal函數不一樣,首先是需要將符合條件的元素里的變量取出來也就是{{a}}里的a,argments[1]就是a變量。在考慮到對象嵌套,就執行上面的getVal。然后就可以將data里的值替換到文本里了。
這樣元素節點和文本節點都編譯完成了。然后將整個虛擬節點丟回dom樹里去 。MVVM的編譯就結束了
第二步:數據劫持,函數很少。但比較繞.這里執行observe,利用遞歸遍歷,將data里的鍵值對全部拿出來處理,執行defineReactive函數,這里18行可以先不看。 看下面的最重點的Object.defineProperty()。這里要傳入劫持的對象,劫持的鍵,以及回調函數。這里回調函數里倆個參數在下圖。
然后,get函數是取值是做對應的操作,set函數是設置值做對應的操作。至此數據劫持就完成了
第三步:watcher 監察者 ,一旦變化執行對應的操作。也就是將模板編譯和數據劫持倆個函數聯系在一起。有銜接。
這里創建watcher類,將需要的參數獲取。 vm是實例,expr是值,cb是回調函數callback。watcher實例里的value = get方法的返回值,value執行一次嵌套處理返回。這里監察者作用主要是 一 更新值,二是執行callback回調函數cb。三將自己的實例,放入dep的target里。那么watcher監察者就制作好了。
最后的連接部分,首先data里的每個屬性值都被加上了set和get
1.獲取值
在最開始編譯的時候,編譯節點的文本節點處理和元素節點處理的時候執行watcher函數,在watcher函數里的get函數中將 watcher函數自己放入de問值的時候,則會執行get函數,將 每個watcher放入dep數組中 。
2.修改值
在修改值的時候,會觸發Observer.js 的defineProperty的set函數,set函數里比較新的值和舊的值,value是編譯時候的值,newValue是set函數的第一個參數,也就是修改后的新值 。 將倆者比較,如果不同,就執行Dep構造函數的notify函數。notify則會遍歷全部存在的dep數組里的watcher的update方法。在watcher的update方法中,比較值的不同,如果不同就則執行回調函數,將視圖更新。這個回調函數是嵌套在處理文本節點和元素節點的方法里。
v-model的雙向綁定
至於v-model的雙向綁定,其實是綁定輸入框的輸入事件。將輸入事件新的值賦值給input節點的value值,然后值的改變,執行set函數,將視圖改變。視圖的改變,會執行wacther的回調函數,文本節點也會重新賦值。
最終效果:
這就是mvvm響應式原理的實現,如果有殘缺講不清楚的地方,歡迎指出。謝謝。