前端MVVM實(shí)戰(zhàn)-模板解析之事件指令和一般指令

模板解析

  • 解析雙括號(hào)
  • v-on綁定事件
  • v-text綁定事件
  • v-class綁定事件
  • v-html綁定事件
  • v-model綁定事件

上一章對(duì)雙括號(hào)進(jìn)行了解析,接下來(lái)開(kāi)始對(duì)事件指令進(jìn)行解析

v-on指令

先初始化代碼

<div id="title">
    <span>{{title}}</span>
    <button v-on:click='showInfo'>點(diǎn)我</button>
</div>
<script>
var mv = new MvvmVue({
    el:'#title',
    data:{
        title:'Hello'
    },
    methods:{
        showInfo(){
            alert('HAHA')
        }
    }
})
</script>

功能需求是事件綁定峦剔,當(dāng)點(diǎn)擊button時(shí)档礁,會(huì)執(zhí)行我們定義的函數(shù),彈出 haha 的信息

首先要明確的一點(diǎn)是在button元素上吝沫,諸如class呻澜,id递礼,以及可以自定義的一些如num,index這些都是一個(gè)元素節(jié)點(diǎn)上的屬性,而對(duì)于屬性可以通過(guò)節(jié)點(diǎn)的attributes就能輕松的獲取到所有的屬性羹幸。

在上一節(jié)脊髓,定義了模板編譯函數(shù)compileFrag,在里面只是對(duì)一個(gè)元素節(jié)點(diǎn)的文本內(nèi)容進(jìn)行了解析和編譯栅受,那么繼續(xù)對(duì)該函數(shù)進(jìn)行擴(kuò)展将硝,讓它支持事件和一般指令的解析。

...
// 進(jìn)行模板編譯
Compile.prototype.compileFrag=function (el){
    // 拿到節(jié)點(diǎn)中的第一層所有子節(jié)點(diǎn)
    var childNodes = el.childNodes
    var _self = this
    Array.prototype.slice.apply(childNodes).forEach(function (node) {
        // 正則找 {{}} 需要拿到里面的字符串
        var reg = /\{\{(.*)\}\}/
        // 判斷是否是元素節(jié)點(diǎn)
        if(_self.isElement(node)){
            // 查看綁定的指令
            // 這里對(duì)v-指令進(jìn)行解析和編譯
            ...
            // 判斷是否是文本且節(jié)點(diǎn)的內(nèi)容能否匹配上正則
        } else if(_self.isText(node) && reg.test(node.textContent)){ 
            // 編譯大括號(hào)模板
            _self.compileText(node, RegExp.$1)

        }
        // 如果子節(jié)點(diǎn)還有子節(jié)點(diǎn)
        if (node.childNodes && node.childNodes.length) {
            // 調(diào)用實(shí)現(xiàn)所有層次節(jié)點(diǎn)的編譯
            _self.compileFrag(node);
        }
    })
}
// 判斷傳過(guò)來(lái)的是否是node節(jié)點(diǎn)
Compile.prototype.isElement = function (node){
    return node.nodeType == 1 // 如果是1的話就是標(biāo)簽
}
...

同大括號(hào)解析一樣,再創(chuàng)建一個(gè) compileV 函數(shù)來(lái)專(zhuān)門(mén)對(duì)v-指令進(jìn)行一個(gè)解析和編譯,該函數(shù)相對(duì)來(lái)說(shuō)比較復(fù)雜弛矛,先從解析事件指令v-on開(kāi)始

// 進(jìn)行模板編譯
Compile.prototype.compileFrag=function (el){
    // 拿到節(jié)點(diǎn)中的第一層所有子節(jié)點(diǎn)
    var childNodes = el.childNodes
    var _self = this
    Array.prototype.slice.apply(childNodes).forEach(function (node) {
        // 正則找 {{}} 需要拿到里面的字符串
        var reg = /\{\{(.*)\}\}/
        // 判斷是否是元素節(jié)點(diǎn)
        if(_self.isElement(node)){
            // 查看綁定的指令
            // 這里對(duì)v-指令進(jìn)行解析和編譯
            _self.compileV(node)
            // 判斷是否是文本且節(jié)點(diǎn)的內(nèi)容能否匹配上正則
        } else if(_self.isText(node) && reg.test(node.textContent)){
            // 編譯大括號(hào)模板
            _self.compileText(node, RegExp.$1)

        }
        // 如果子節(jié)點(diǎn)還有子節(jié)點(diǎn)
        if (node.childNodes && node.childNodes.length) {
            // 調(diào)用實(shí)現(xiàn)所有層次節(jié)點(diǎn)的編譯
            _self.compileFrag(node);
        }
    })
}

compileV接收一個(gè)node參數(shù)羔味,node是當(dāng)前綁定的節(jié)點(diǎn)

...
// 對(duì)指令進(jìn)行編譯
Compile.prototype.compileV = function (node){
// 接收上面?zhèn)鱽?lái)的node節(jié)點(diǎn)
    var _self = this
    var vm = this._vm
    var attrs = node.attributes // 得到該node的所有的屬性

    // 遍歷該屬性數(shù)組
    Array.prototype.slice.apply(attrs).forEach(function (attr) {
        // 得到指令名字符串 如:v-on:click
        var attrName = attr.name
        // 判斷是否有指令屬性
        // 這里把 @ 的寫(xiě)法考慮進(jìn)去
        if(attrName.indexOf('v-') === 0 || attrName.indexOf('@') === 0 ){

            var attrValue = attr.value.trim() // 得到屬性的值
            // 由于事件綁定的處理和其他的指定綁定處理不一樣所以分開(kāi)處理
            console.log(attrName.indexOf('v-'))
            // 這里對(duì)屬性字符串進(jìn)行了一次截取排作,也可以不用截取但是全等后面數(shù)字要變成2
            if(attrName.substring(2).indexOf('on')===0 || attrName.indexOf('@') === 0){
                // 事件綁定
                ....
            }
            // 最后移除該指令在node上
            node.removeAttribute(attrName)
        }
    })
}
...

上面主要是對(duì)屬性的一個(gè)辨別和分析,然后需要處理的就是分辨該指令是什么事件(click?mousemove?)的指令误辑,這里就需要對(duì)使用該指令的節(jié)點(diǎn)進(jìn)行事件綁定,這就涉及到了原生的事件綁定,注意歌逢,最后使用了removeAttribute將屬性從節(jié)點(diǎn)上刪除巾钉,是因?yàn)槌鲇诎踩痛a簡(jiǎn)潔考慮,不會(huì)把一些指令屬性留下來(lái)

新增handelFn函數(shù)秘案,進(jìn)行節(jié)點(diǎn)的事件綁定

...
// 對(duì)指令進(jìn)行編譯
Compile.prototype.compileV = function (node){
    var _self = this
    var vm = this._vm
    var attrs = node.attributes // 得到該node的所有的屬性

    // 遍歷該屬性數(shù)組
    Array.prototype.slice.apply(attrs).forEach(function (attr) {
        // 得到指令名字符串 如:v-on:click
        var attrName = attr.name
        // 判斷是否有指令屬性
        if(attrName.indexOf('v-') === 0 || attrName.indexOf('@') === 0 ){

            var attrValue = attr.value.trim() // 得到屬性的值
            // 由于事件綁定的處理和其他的指定綁定處理不一樣所以分開(kāi)處理
            console.log(attrName.indexOf('v-'))
            if(attrName.substring(2).indexOf('on')===0 || attrName.indexOf('@') === 0){

                // 事件綁定
                // 在這里做安全判斷
                // 需要判斷的是屬性值砰苍,data中是否有methodes聲明,以及是否有匹配的函數(shù)
                if(attrValue && vm.$options.methods && vm.$options.methods[attrValue]){
                    // 另外寫(xiě)一個(gè)函數(shù)進(jìn)行事件綁定
                    // 進(jìn)行函數(shù)綁定
                    _self.handelFn(node, attrName, vm.$options.methods[attrValue])
                }
            }

            // 最后移除該指令在node上
            node.removeAttribute(attrName)
        }
    })

}
...

編寫(xiě)事件綁定踏烙,這個(gè)很簡(jiǎn)單

...
// 進(jìn)行回調(diào)綁定
Compile.prototype.handelFn = function (node, fnType, fn){
    if(fnType.indexOf('v-') === 0 ){
        var fnType = fnType.split(':')[1]
    } else if( fnType.indexOf('@') === 0 ){
        var fnType = fnType.split('@')[1]
    }
    // 這里進(jìn)行函數(shù)綁定
    // 注意這里需要對(duì)fn進(jìn)行bind修改this指向师骗,并且把this指向MvvmVue實(shí)例對(duì)象历等,否則該fn的this指向的是事件綁定者
    node.addEventListener(fnType, fn.bind(this._vm) ,false)
}
...

最后來(lái)點(diǎn)擊一下讨惩,button按鈕

成功彈出,解析成功

一般指令

  • v-text指令
  • v-class指令
  • v-html指令

這幾個(gè)指令就一起寫(xiě)吧寒屯,大體都差不多

gogo<瞿怼!

初始化代碼

<style type="text/css" media="screen">
    .redC{
        width:200px;
        height:200px;
        background-color:red;
    }
</style>
<div id="title">
    <span v-text='title'></span>
    <span v-html='htmlS'></span>
    <div v-class='redC'></div>
</div>
<script>
var mv = new MvvmVue({
    el:'#title',
    data:{
        title:'Hello',
        htmlS:'<p>World</p>',
        redC:'redC'
    }
})
</script>

哦K寡夹,回到之前的compileV函數(shù)处面,在這個(gè)函數(shù)里面,主要是對(duì)節(jié)點(diǎn)的屬性進(jìn)行一個(gè)解析菩掏,以及只對(duì)v-on指令的解析和事件綁定魂角,接下來(lái)加個(gè)else繼續(xù)從這個(gè)函數(shù)入手,解析上面幾個(gè)一般指令

...
// 對(duì)指令進(jìn)行編譯
Compile.prototype.compileV = function (node){
    var _self = this
    var vm = this._vm
    var attrs = node.attributes // 得到該node的所有的屬性

    // 遍歷該屬性數(shù)組
    Array.prototype.slice.apply(attrs).forEach(function (attr) {
        // 得到指令名字符串 如:v-on:click
        var attrName = attr.name
        // 判斷是否有指令屬性
        if(attrName.indexOf('v-') === 0 || attrName.indexOf('@') === 0 ){

            var attrValue = attr.value.trim() // 得到屬性的值
            // 由于事件綁定的處理和其他的指定綁定處理不一樣所以分開(kāi)處理
            console.log(attrName.indexOf('v-'))
            if(attrName.substring(2).indexOf('on')===0 || attrName.indexOf('@') === 0){

                // 事件綁定
                // 在這里做安全判斷
                if(attrValue && vm.$options.methodes && vm.$options.methodes[attrValue]){
                    // 進(jìn)行函數(shù)綁定
                    _self.handelFn(node, attrName, vm.$options.methodes[attrValue])
                }
            } else {
                // 處理其他指令
                ...
            }
            // 最后移除該指令在node上
            node.removeAttribute(attrName)
        }
    })
}
...

還記得上一章聲明的工具函數(shù)vUtil嗎智绸,指令的屬性處理就全權(quán)交給它來(lái)處理

首先野揪,能明確一點(diǎn)的是访忿,通過(guò)compileV解析,能夠知道指令名斯稳,其綁定的節(jié)點(diǎn)以及指令上的值海铆,知道這些就好做了,不外乎就是解析值然后再通過(guò)鍵從data中拿值挣惰,最后再根據(jù)相應(yīng)的指令進(jìn)行各自的處理

敲簡(jiǎn)單

// 接收三個(gè)參數(shù)
// vm實(shí)例 為了拿到data的值
// node節(jié)點(diǎn)
// value指令上的值
Compile.prototype.vUtil = {
    // v-text
    text: function (vm, node, value) {
    // 同樣用到了getDataValue來(lái)拿data里面的值
       node.textContent = this.getDataValue(vm,value) ? this.getDataValue(vm,value)  : ''
    },
    // v-html
    html:function (vm, node, value) {
        node.innerHTML = this.getDataValue(vm,value) ? this.getDataValue(vm,value) : ''
    },
    // v-class
    class: function (vm, node, value) {
        var nodeClass = node.className
        // 對(duì)class進(jìn)行賦值的時(shí)候需要考慮其自身已經(jīng)定義的class
        node.className = nodeClass? nodeClass+ ' ' + this.getDataValue(vm,value):this.getDataValue(vm,value);
    },
    // 得到{{}}中指定的數(shù)據(jù)卧斟,從實(shí)例中得到
    getDataValue: function (vm, regStr){
        var val = vm._data
        regStr = regStr.split('.')
        regStr.forEach(function (k) {
            val = val[k]
        })
        return val
    }
}

上面的代碼已經(jīng)能能夠解析這三種指令,但是我希望每個(gè)函數(shù)里面的功能盡量少且最好是一個(gè)函數(shù)只做一件事情憎茂,所以做一些優(yōu)化珍语,增加兩個(gè)函數(shù)

Compile.prototype.vUtil = {
    // v-text/ {{}}
    text: function (vm, node, value) {
        this.deal(vm, node, value, 'text')
    },
    // v-html
    html:function (vm, node, value) {
        this.deal(vm, node, value, 'html')
    },
    // v-class
    class: function (vm, node, value) {
        this.deal(vm, node, value, 'class')
    },
    // 這個(gè)函數(shù)統(tǒng)一進(jìn)行指令的處理
    deal: function (vm, node, value, type) {
        var _self = this;
        // 這里需要處理不同的指令
        this.dealTypeFn[type+'Updata'] && this.dealTypeFn[type+'Updata'](node, this.getDataValue(vm,value))
    },
    dealTypeFn:{
        textUpdata: function (node,value) {
            node.textContent = value ? value  : ''
        },
        htmlUpdata: function (node, value) {
            node.innerHTML = value ? value : ''
        },
        classUpdata: function (node, value) {
            var nodeClass = node.className
            node.className = nodeClass? nodeClass+ ' ' + value:value;
        }
    },
    getDataValue: function (vm, regStr){
        var val = vm._data
        regStr = regStr.split('.')
        regStr.forEach(function (k) {
            val = val[k]
        })
        return val
    }
}

最后在compileV中的else中添加處理函數(shù)

// 對(duì)指令進(jìn)行編譯
Compile.prototype.compileV = function (node){
    var _self = this
    var vm = this._vm
    var attrs = node.attributes // 得到該node的所有的屬性
    // 遍歷該屬性數(shù)組
    Array.prototype.slice.apply(attrs).forEach(function (attr) {
        // 得到指令名字符串 如:v-on:click
        var attrName = attr.name
        // 判斷是否有指令屬性
        if(attrName.indexOf('v-') === 0 || attrName.indexOf('@') === 0 ){
            var attrValue = attr.value.trim() // 得到屬性的值
            // 由于事件綁定的處理和其他的指定綁定處理不一樣所以分開(kāi)處理
            if(attrName.substring(2).indexOf('on')===0 || attrName.indexOf('@') === 0){
                // 事件綁定
                // 在這里做安全判斷
                if(attrValue && vm.$options.methods && vm.$options.methods[attrValue]){
                    // 進(jìn)行函數(shù)綁定
                    _self.handelFn(node, attrName, vm.$options.methods[attrValue])
                }
            } else {
                // 處理其他指令
                // 得到對(duì)應(yīng)的指令名 如: html,text
                var name = attrName.substring(2)
                _self.vUtil[name] && _self.vUtil[name](vm, node, attrValue)
            }

            // 最后移除該指令在node上
            node.removeAttribute(attrName)
        }
    })

}

執(zhí)行下初始代碼,如下圖

8.png

所有指令都能正確的取到data里的數(shù)據(jù)竖幔,目前除了v-model廊酣,一般指令和事件指令都已解析成功,由于v-model涉及到了雙向數(shù)據(jù)綁定赏枚,在后面雙向數(shù)據(jù)綁定的時(shí)候會(huì)詳細(xì)去寫(xiě)亡驰。

...還沒(méi)有完,再做一些代碼優(yōu)化饿幅,在上一章對(duì)雙括號(hào)進(jìn)行解析賦值時(shí)使用的compileText函數(shù)凡辱,不過(guò)我們是在函數(shù)內(nèi)部直接對(duì)node.textContent進(jìn)行的賦值,在這一章里面在vUtil函數(shù)里命名了多個(gè)指令解析函數(shù)栗恩,其中text的功能完全可以復(fù)用到雙括號(hào)的解析上透乾,那么把compileText修改一下

// 對(duì)文本節(jié)點(diǎn)進(jìn)行賦值
Compile.prototype.compileText = function (node, regStr){
    // 得到相對(duì)應(yīng)的數(shù)據(jù),然后進(jìn)行內(nèi)容填充
    this.vUtil.text(this._vm, node, regStr) // 使用text來(lái)進(jìn)行賦值
    // node.textContent = this.vUtil.getDataValue(this._vm, regStr)
}

哦K磕秤,那么接下來(lái)就是數(shù)據(jù)綁定了....

其他文章導(dǎo)航:

前端MVVM理論-MVC和MVP
前端MVVM理論-MVVM
前端MVVM實(shí)戰(zhàn)-常用的幾個(gè)方法和屬性
前端MVVM實(shí)戰(zhàn)-數(shù)據(jù)代理
前端MVVM實(shí)戰(zhàn)-模板解析之雙括號(hào)解析
前端MVVM實(shí)戰(zhàn)-模板解析之事件指令和一般指令
前端MVVM實(shí)戰(zhàn)-數(shù)據(jù)綁定(一)
前端MVVM實(shí)戰(zhàn)-數(shù)據(jù)綁定(二)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末乳乌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子市咆,更是在濱河造成了極大的恐慌汉操,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒙兰,死亡現(xiàn)場(chǎng)離奇詭異磷瘤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)搜变,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)采缚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人挠他,你說(shuō)我怎么就攤上這事扳抽。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵贸呢,是天一觀的道長(zhǎng)赂苗。 經(jīng)常有香客問(wèn)我,道長(zhǎng)贮尉,這世上最難降的妖魔是什么拌滋? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮猜谚,結(jié)果婚禮上败砂,老公的妹妹穿的比我還像新娘。我一直安慰自己魏铅,他們只是感情好昌犹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著览芳,像睡著了一般斜姥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沧竟,一...
    開(kāi)封第一講書(shū)人閱讀 51,598評(píng)論 1 305
  • 那天铸敏,我揣著相機(jī)與錄音,去河邊找鬼悟泵。 笑死杈笔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的糕非。 我是一名探鬼主播蒙具,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼朽肥!你這毒婦竟也來(lái)了禁筏?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤衡招,失蹤者是張志新(化名)和其女友劉穎篱昔,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蚁吝,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡旱爆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年舀射,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了窘茁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脆烟,死狀恐怖山林,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤驼抹,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布桑孩,位于F島的核電站,受9級(jí)特大地震影響框冀,放射性物質(zhì)發(fā)生泄漏流椒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一明也、第九天 我趴在偏房一處隱蔽的房頂上張望宣虾。 院中可真熱鬧,春花似錦温数、人聲如沸绣硝。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鹉胖。三九已至,卻和暖如春够傍,著一層夾襖步出監(jiān)牢的瞬間甫菠,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工冕屯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留淑蔚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓愕撰,卻偏偏與公主長(zhǎng)得像刹衫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子搞挣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容