模板解析
- 解析雙括號(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í)行下初始代碼,如下圖
所有指令都能正確的取到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ù)綁定(二)