在實(shí)現(xiàn)雙向數(shù)據(jù)綁定的之前,先來實(shí)現(xiàn)單向數(shù)據(jù)綁定,首先,我們的HTML結(jié)構(gòu)如下圖所示。
<div id="app">
????<input type="text" v-model="msg">
????<p>{{msg}}</p>
????<ul>
????????<li>1</li>
????????<li>{{msg}}</li>
????????<li>{{test}}</li>
????</ul>
</div>
接下來就是在JS里面實(shí)現(xiàn)單向數(shù)據(jù)綁定了,不管怎么樣,我們就像Vue一樣,直接來創(chuàng)建個(gè)Vue實(shí)例化對(duì)象。
new Vue({
????el:"app",
????data:{
????????msg:"hello world",
????????test:"單向綁定"
????}
});
而此時(shí)根本就沒有Vue這個(gè)類,所以下一步直接來創(chuàng)建這個(gè)類,在這個(gè)類當(dāng)中,我們將傳遞過來的el屬性值賦值給id(該例中以id為例),并且也用一個(gè)data來保存?zhèn)鬟f過來的data信息。
function Vue(options){
????this.id=options.el;
? ??this.data=options.data;
}
好了,那么下一步的問題就是如何實(shí)現(xiàn)單向數(shù)據(jù)綁定的問題了,在Vue當(dāng)中,我們可以通過v-model這樣的屬性名或者{{}}的形式來進(jìn)行綁值操作,很顯然,我們是需要依次獲取到整個(gè)id為app標(biāo)簽下的所有node節(jié)點(diǎn),當(dāng)其是元素節(jié)點(diǎn)的時(shí)候,就要查看其屬性名是否含有v-model,如果有,根據(jù)其設(shè)置的value值對(duì)應(yīng)的去Vue實(shí)例化對(duì)象的data屬性中查找并賦值;當(dāng)其是文本節(jié)點(diǎn)的時(shí)候,就需要匹配{{}}里面的內(nèi)容,然后同上操作。
為了講解方便,就先來實(shí)現(xiàn)根據(jù)節(jié)點(diǎn)所屬類別來進(jìn)行賦值操作的方法,該方法需要兩個(gè)參數(shù),一個(gè)參數(shù)為當(dāng)前的節(jié)點(diǎn),一個(gè)為實(shí)例化的Vue對(duì)象。
function compile(node,vm){
? ??//當(dāng)node節(jié)點(diǎn)為元素節(jié)點(diǎn)時(shí)
? ??if(node.nodeType === 1){
? ??????var attr=node.attributes;
? ??????//遍歷當(dāng)前節(jié)點(diǎn)的所有屬性
? ??????for(let [i,v] of [].entries.call(attr)){
? ??????????if(v.nodeName=="v-model"){
? ??????????????var name=v.nodeValue;
? ??????????????node.value=vm.data[name];
? ??????????????node.removeAttribute(v.nodeName);
? ? ? ? ? ? ? }????
? ?????}
????}
? ??//當(dāng)node節(jié)點(diǎn)為文本節(jié)點(diǎn)時(shí)
? ??if(node.nodeType === 3){
? ??????????var reg=/\{\{(.*)\}\}/;
? ??????????if(reg.test(node.nodeValue.trim())){
? ??????????????????var name=RegExp.$1;
? ? ? ? ? ? ? ? ? ? ?node.nodeValue=vm.data[name];
? ? ? ? ? ? ?}
? ??}
}
然后很重要的一步就是獲取到id為app元素節(jié)點(diǎn)下的所有節(jié)點(diǎn),調(diào)用剛剛封裝好了的compile方法即可,那么如何來獲取到這所謂的所有節(jié)點(diǎn)呢?小伙伴們可能一下子就想到了節(jié)點(diǎn)屬性上有一個(gè)childNodes,然后循環(huán)這里的每一項(xiàng),如果該項(xiàng)存在著子節(jié)點(diǎn)的話,可以遞歸調(diào)用該方法,emm,聽起來很有道理的感覺,代碼如下圖所示。

但是這么運(yùn)行起來的時(shí)候結(jié)果并沒有達(dá)到預(yù)期的效果,問題出現(xiàn)在哪了呢?說實(shí)話,我也踩了一下這個(gè)坑,思考了一下才反應(yīng)過來,這主要是由于DOM映射導(dǎo)致的,我們可以試著打印出循環(huán)的結(jié)果,可以發(fā)現(xiàn)只打印出了4次,而并沒有將里面的7項(xiàng)都給輸出,至于DOM映射機(jī)制在本篇文章當(dāng)中就不進(jìn)行過多的介紹,詳情可百度或參考我的個(gè)人博客。
既然知道了是DOM映射導(dǎo)致的問題,那么又該如何去解決呢,其實(shí)很簡單,在存在DOM映射的情況下,每一次node.childNodes的長度都會(huì)減一,并且去除的這一個(gè)正好是之前數(shù)組的第一項(xiàng),故而我們只需要每一次拿到node.childNodes的第一項(xiàng)就可以了。

那么除了for循環(huán)實(shí)現(xiàn)方式以外,有沒有其它更好的實(shí)現(xiàn)方式呢,答案是有的,還是類似的邏輯,我們可以用while來代替for循環(huán),只要我們的node存在第一個(gè)子節(jié)點(diǎn)(firstChild)就可以了。

好了,到此為止,咱們就實(shí)現(xiàn)了單向數(shù)據(jù)綁定的操作,下一篇咱們再深入去實(shí)現(xiàn)雙向數(shù)據(jù)綁定的騷操作吧!

