如何從0一步步寫出MVVM的框架vue(1)

從vue的的使用開始想,開始寫

  1. vue的基本使用我想大家都會了,看下面的代碼
 <div id="app">
        <input type="text" v-model="inputVal" style="width:600px"> 
        <br />
        這是輸入框的值:{{inputVal}}
        <hr />
        我是{{person.name}},
        <br />
        我今年 {{person.age}}
        <ul>
            <li>das</li>
            <li>asd</li>
        </ul>
 </div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
        new Vue({
            el:"#app",
            data:{
                inputVal: '輸入框的值',
                person:{
                    name: 'jack',
                    age: 18
                }
            }
        })
</script>

最基本和重要的兩點:

  • 數(shù)據(jù)的雙向綁定(input值變,下面inputVal的值也變)
  • 模版渲染數(shù)據(jù)(將person的值渲染到了頁面中去)
  1. 先不考慮這兩點,我們該如何去實現(xiàn)我們的vue;
  • new Vue ,那說明vue一定是一個構(gòu)造函數(shù)
  • 傳遞了兩個參數(shù)(后期在擴充)el, data
    ok,那我們基于這兩個點,先寫一個構(gòu)造函數(shù)vue
class Vue{
    constructor(options){
        this.$el = options.el;
        this.$data = options.data;
        if(this.$el){
            new Compiler(this.$el,this.$data,this)
        }
    }
}

顯而易見,這里面就是一個簡單的數(shù)據(jù)保存操作,然后我們需要新建一個編譯類,這個類的目的就是為了編譯,讓vue這個基礎類不做太多的邏輯處理,只是一個接受數(shù)據(jù),來調(diào)度這個項目,便于維護,這是一個編程習慣吧!
ok,現(xiàn)在就去看看complier這個類怎么去做的。

  1. 創(chuàng)建好vue基類以后,去創(chuàng)建編譯類方法,complier;

明確好這個complier類是要做什么

  • 把#app里面的dom節(jié)點全部轉(zhuǎn)移到fragment這個文檔隨便中去
  • 編譯我們的fragment里面的文檔碎片的內(nèi)容,將數(shù)據(jù)映射到視圖
  • 再將fragment添加回#app中去;
    看代碼
class Compiler{
    constructor(el,data,vm){
        this.$el = el;
        this.data = data;
        this.vm = vm;
        let node = this.isElementNode(this.$el) ? this.$el : document.querySelector(options.el)
        // 避免重繪回流,一次性將節(jié)點全部加載到文檔碎片中去
        let fragment = this.nodeToFragment(node);
        // 編譯
        this.compile(fragment)
        // 添加到文檔中去
        node.appendChild(fragment)
    }

    // 判斷用戶傳進來的是不是node節(jié)點
    isElementNode(el){
        return el.nodeType === 1;
    }
}

上面的代碼注釋已經(jīng)很詳細,大家又不懂的可以留言
上面這些代碼完成了第一步的需求了,然后我們開始最重要的第二步;
這個compile函數(shù)也是最關鍵的,去看看compile做了什么把;

// 編譯
    compile(fragment){
        [...fragment.childNodes].forEach((e)=>{
            if(e.nodeType === 1){
                // 因為里面還有子元素,所以還需要在遍歷一遍
                this.compile(e);
                [...e.attributes].forEach((attr,index)=>{
                    // arrt是個對象
                    let { name ,value } = attr;
                    name.startsWith('v-') && this.handleElementNode(name,value,e);
                })
            }
            if(e.nodeType === 3){
                let reg = /\{\{.+?\}\}/;
                reg.test(e.textContent) && this.handleTextNode(e.textContent,e);
            }
        })
    }

接受轉(zhuǎn)換好的fragment這個文檔碎片進行遍歷操作,
切記:這里面的fragment.childNodes是一個類數(shù)組,必須轉(zhuǎn)為數(shù)組在能操作

然后我們進行判斷,看他是一個元素節(jié)點還是一個文本節(jié)點。如果是一個元素節(jié)點,那我們繼續(xù)遞歸,讀出里面的子節(jié)點,直到文本節(jié)點為止;

  1. 如果是元素節(jié)點,那我們?nèi)∠滤麄兊膶傩裕纯从袥]有v-開頭的屬性,我們?nèi)〕鰜恚?br> 提示:然后對屬性再進行遍歷,遍歷的子節(jié)點是一個對象,對象,對象,真的很奇怪。。。,然后通過結(jié)構(gòu)的方法我們拿到了v-model和后面的表達式;
    然后取出來我們在調(diào)用handleElementNode的方法轉(zhuǎn)換,傳入指令和指令后面的表達式,節(jié)點;等下看這個方法;

  2. 如果是文本節(jié)點,我們需要匹配到{{}} 這種差值表達式,那利用差值表達式即可,如果符合這種我們就調(diào)用handleTextNode方法,傳入文本值和節(jié)點;

  3. handleElementNode && handleTextNode

 // 轉(zhuǎn)換指令元素類
    handleElementNode(attr,val,node){
        let value = this.getVal(val);
        let [,dir] = attr.split('-');
        CompileUtil[dir](node,value)
    }

    // 轉(zhuǎn)換差值表達式 文本類
    handleTextNode(val,node){
        let reg = /\{\{(.+?)\}\}/;
        let newVal = val.replace(reg, (...arg) => {
            return this.getVal(arg[1])
        });
        console.log(node)
        node.textContent = newVal;
    }

    // 根據(jù)傳入的表達式獲取data的數(shù)據(jù)
    getVal(expr){
        let data = this.data;
        return expr.split('.').reduce((pre,cur)=>{
            return pre[cur]
        },data)
    }

上面的代碼很簡單,獲取值,然后賦值;

  • 就是getVal的方法不好理解,就是我們傳入‘person.age’這樣的表達式,然后去data上面去取值,顯然我們需要先轉(zhuǎn)為數(shù)組然后依次去取,那么reduce最好實現(xiàn)不過了,這個在lodash庫中就是get方法,不理解也可以用數(shù)組循環(huán)做;

  • 還有一個replace方法,很顯然我們有兩個不同的正則表達式,
    let reg1 = /{{.+?}}/;
    let reg2 = /{{(.+?)}}/;
    下面就是比上面多一個括號,這個作用就是當我匹配到括號里面的內(nèi)容的時候拿出來成為一個分組,我在match replace這種方法里面就能拿到分組,就是我們拿到了{{ person.name }}中的person.name;上面只是用來匹配這樣的字符串,具體可以看replace方法,還可以看我git上面有一個關于正則的學習筆記地址;

  • 在編譯handleElementNode中有一個CompileUtil函數(shù),我們在這里其實有一個CompileUtil工具函數(shù),具體如下:

CompileUtil = {
   model(node,val){
       node.value = val;
   },
   html(){},
   text(){},
}

大家如果用過vue,那么一看就知道啥意思了,不做多解釋了;


到現(xiàn)在為止,我們完成了第一步,data渲染成了view了,不信大家可以看看,html頁面有沒有渲染完成,然后我們進行比較重要的一步了,那就是數(shù)據(jù)雙向綁定了;先歇一會把。在第二節(jié)接著去實現(xiàn)把,一口氣打了這么多字有點小累了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容