前端MVVM实战-模板解析之事件指令和一般指令

模板解析

  • 解析双括号
  • v-on绑定事件
  • v-text绑定事件
  • v-class绑定事件
  • v-html绑定事件
  • v-model绑定事件

上一章对双括号进行了解析,接下来开始对事件指令进行解析

v-on指令

先初始化代码

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

功能需求是事件绑定,当点击button时,会执行我们定义的函数,弹出 haha 的信息

首先要明确的一点是在button元素上,诸如class,id,以及可以自定义的一些如num,index这些都是一个元素节点上的属性,而对于属性可以通过节点的attributes就能轻松的获取到所有的属性。

在上一节,定义了模板编译函数compileFrag,在里面只是对一个元素节点的文本内容进行了解析和编译,那么继续对该函数进行扩展,让它支持事件和一般指令的解析。

...
// 进行模板编译
Compile.prototype.compileFrag=function (el){
    // 拿到节点中的第一层所有子节点
    var childNodes = el.childNodes
    var _self = this
    Array.prototype.slice.apply(childNodes).forEach(function (node) {
        // 正则找 {{}} 需要拿到里面的字符串
        var reg = /\{\{(.*)\}\}/
        // 判断是否是元素节点
        if(_self.isElement(node)){
            // 查看绑定的指令
            // 这里对v-指令进行解析和编译
            ...
            // 判断是否是文本且节点的内容能否匹配上正则
        } else if(_self.isText(node) && reg.test(node.textContent)){ 
            // 编译大括号模板
            _self.compileText(node, RegExp.$1)

        }
        // 如果子节点还有子节点
        if (node.childNodes && node.childNodes.length) {
            // 调用实现所有层次节点的编译
            _self.compileFrag(node);
        }
    })
}
// 判断传过来的是否是node节点
Compile.prototype.isElement = function (node){
    return node.nodeType == 1 // 如果是1的话就是标签
}
...

同大括号解析一样,再创建一个 compileV 函数来专门对v-指令进行一个解析和编译,该函数相对来说比较复杂,先从解析事件指令v-on开始

// 进行模板编译
Compile.prototype.compileFrag=function (el){
    // 拿到节点中的第一层所有子节点
    var childNodes = el.childNodes
    var _self = this
    Array.prototype.slice.apply(childNodes).forEach(function (node) {
        // 正则找 {{}} 需要拿到里面的字符串
        var reg = /\{\{(.*)\}\}/
        // 判断是否是元素节点
        if(_self.isElement(node)){
            // 查看绑定的指令
            // 这里对v-指令进行解析和编译
            _self.compileV(node)
            // 判断是否是文本且节点的内容能否匹配上正则
        } else if(_self.isText(node) && reg.test(node.textContent)){
            // 编译大括号模板
            _self.compileText(node, RegExp.$1)

        }
        // 如果子节点还有子节点
        if (node.childNodes && node.childNodes.length) {
            // 调用实现所有层次节点的编译
            _self.compileFrag(node);
        }
    })
}

compileV接收一个node参数,node是当前绑定的节点

...
// 对指令进行编译
Compile.prototype.compileV = function (node){
// 接收上面传来的node节点
    var _self = this
    var vm = this._vm
    var attrs = node.attributes // 得到该node的所有的属性

    // 遍历该属性数组
    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() // 得到属性的值
            // 由于事件绑定的处理和其他的指定绑定处理不一样所以分开处理
            console.log(attrName.indexOf('v-'))
            // 这里对属性字符串进行了一次截取,也可以不用截取但是全等后面数字要变成2
            if(attrName.substring(2).indexOf('on')===0 || attrName.indexOf('@') === 0){
                // 事件绑定
                ....
            }
            // 最后移除该指令在node上
            node.removeAttribute(attrName)
        }
    })
}
...

上面主要是对属性的一个辨别和分析,然后需要处理的就是分辨该指令是什么事件(click?mousemove?)的指令,这里就需要对使用该指令的节点进行事件绑定,这就涉及到了原生的事件绑定,注意,最后使用了removeAttribute将属性从节点上删除,是因为出于安全和代码简洁考虑,不会把一些指令属性留下来

新增handelFn函数,进行节点的事件绑定

...
// 对指令进行编译
Compile.prototype.compileV = function (node){
    var _self = this
    var vm = this._vm
    var attrs = node.attributes // 得到该node的所有的属性

    // 遍历该属性数组
    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() // 得到属性的值
            // 由于事件绑定的处理和其他的指定绑定处理不一样所以分开处理
            console.log(attrName.indexOf('v-'))
            if(attrName.substring(2).indexOf('on')===0 || attrName.indexOf('@') === 0){

                // 事件绑定
                // 在这里做安全判断
                // 需要判断的是属性值,data中是否有methodes声明,以及是否有匹配的函数
                if(attrValue && vm.$options.methods && vm.$options.methods[attrValue]){
                    // 另外写一个函数进行事件绑定
                    // 进行函数绑定
                    _self.handelFn(node, attrName, vm.$options.methods[attrValue])
                }
            }

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

}
...

编写事件绑定,这个很简单

...
// 进行回调绑定
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]
    }
    // 这里进行函数绑定
    // 注意这里需要对fn进行bind修改this指向,并且把this指向MvvmVue实例对象,否则该fn的this指向的是事件绑定者
    node.addEventListener(fnType, fn.bind(this._vm) ,false)
}
...

最后来点击一下,button按钮

成功弹出,解析成功

一般指令

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

这几个指令就一起写吧,大体都差不多

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函数,在这个函数里面,主要是对节点的属性进行一个解析,以及只对v-on指令的解析和事件绑定,接下来加个else继续从这个函数入手,解析上面几个一般指令

...
// 对指令进行编译
Compile.prototype.compileV = function (node){
    var _self = this
    var vm = this._vm
    var attrs = node.attributes // 得到该node的所有的属性

    // 遍历该属性数组
    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() // 得到属性的值
            // 由于事件绑定的处理和其他的指定绑定处理不一样所以分开处理
            console.log(attrName.indexOf('v-'))
            if(attrName.substring(2).indexOf('on')===0 || attrName.indexOf('@') === 0){

                // 事件绑定
                // 在这里做安全判断
                if(attrValue && vm.$options.methodes && vm.$options.methodes[attrValue]){
                    // 进行函数绑定
                    _self.handelFn(node, attrName, vm.$options.methodes[attrValue])
                }
            } else {
                // 处理其他指令
                ...
            }
            // 最后移除该指令在node上
            node.removeAttribute(attrName)
        }
    })
}
...

还记得上一章声明的工具函数vUtil吗,指令的属性处理就全权交给它来处理

首先,能明确一点的是,通过compileV解析,能够知道指令名,其绑定的节点以及指令上的值,知道这些就好做了,不外乎就是解析值然后再通过键从data中拿值,最后再根据相应的指令进行各自的处理

敲简单

// 接收三个参数
// vm实例 为了拿到data的值
// node节点
// value指令上的值
Compile.prototype.vUtil = {
    // v-text
    text: function (vm, node, value) {
    // 同样用到了getDataValue来拿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
        // 对class进行赋值的时候需要考虑其自身已经定义的class
        node.className = nodeClass? nodeClass+ ' ' + this.getDataValue(vm,value):this.getDataValue(vm,value);
    },
    // 得到{{}}中指定的数据,从实例中得到
    getDataValue: function (vm, regStr){
        var val = vm._data
        regStr = regStr.split('.')
        regStr.forEach(function (k) {
            val = val[k]
        })
        return val
    }
}

上面的代码已经能能够解析这三种指令,但是我希望每个函数里面的功能尽量少且最好是一个函数只做一件事情,所以做一些优化,增加两个函数

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')
    },
    // 这个函数统一进行指令的处理
    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中添加处理函数

// 对指令进行编译
Compile.prototype.compileV = function (node){
    var _self = this
    var vm = this._vm
    var attrs = node.attributes // 得到该node的所有的属性
    // 遍历该属性数组
    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() // 得到属性的值
            // 由于事件绑定的处理和其他的指定绑定处理不一样所以分开处理
            if(attrName.substring(2).indexOf('on')===0 || attrName.indexOf('@') === 0){
                // 事件绑定
                // 在这里做安全判断
                if(attrValue && vm.$options.methods && vm.$options.methods[attrValue]){
                    // 进行函数绑定
                    _self.handelFn(node, attrName, vm.$options.methods[attrValue])
                }
            } else {
                // 处理其他指令
                // 得到对应的指令名 如: html,text
                var name = attrName.substring(2)
                _self.vUtil[name] && _self.vUtil[name](vm, node, attrValue)
            }

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

}

执行下初始代码,如下图

8.png

所有指令都能正确的取到data里的数据,目前除了v-model,一般指令和事件指令都已解析成功,由于v-model涉及到了双向数据绑定,在后面双向数据绑定的时候会详细去写。

...还没有完,再做一些代码优化,在上一章对双括号进行解析赋值时使用的compileText函数,不过我们是在函数内部直接对node.textContent进行的赋值,在这一章里面在vUtil函数里命名了多个指令解析函数,其中text的功能完全可以复用到双括号的解析上,那么把compileText修改一下

// 对文本节点进行赋值
Compile.prototype.compileText = function (node, regStr){
    // 得到相对应的数据,然后进行内容填充
    this.vUtil.text(this._vm, node, regStr) // 使用text来进行赋值
    // node.textContent = this.vUtil.getDataValue(this._vm, regStr)
}

哦K,那么接下来就是数据绑定了....

其他文章导航:

前端MVVM理论-MVC和MVP
前端MVVM理论-MVVM
前端MVVM实战-常用的几个方法和属性
前端MVVM实战-数据代理
前端MVVM实战-模板解析之双括号解析
前端MVVM实战-模板解析之事件指令和一般指令
前端MVVM实战-数据绑定(一)
前端MVVM实战-数据绑定(二)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容

  • 模板解析 解析双括号v-on绑定事件v-text绑定事件v-class绑定事件v-html绑定事件v-model绑...
    孤香远阅读 1,512评论 0 1
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,101评论 1 32
  • 一、了解Vue.js 1.1.1 Vue.js是什么? 简单小巧、渐进式、功能强大的技术栈 1.1.2 为什么学习...
    蔡华鹏阅读 3,323评论 0 3
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,314评论 0 9
  • 第3章 基本概念 3.1 语法 3.2 关键字和保留字 3.3 变量 3.4 数据类型 5种简单数据类型:Unde...
    RickCole阅读 5,126评论 0 21