前端MVVM实战-模板解析之双括号解析

模板解析

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

解析双括号

先初始化代码

...
<div id="title">
    <span>{{title}}</span>
</div>
<script>
var mv = new MvvmVue({
    el:'#title',
    data:{
        title:'Hello'
    }
})
</script>
...

对于双括号的解析,从上面可以看出需要的是对节点的判断和节点文本值的解析,这样我们前面定义的el:'#title'就起到了关键作用,通过所传入的id能快速的找到我们的模板。

新建一个complie.js文件,创建一个Compile类,用来专门处理模板的解析,这个构造函数需要接收模板的id,以及MvvmVue实例,后者是用来进行数据获取的,毕竟最后是要把数据渲染到HTML上,然后在MvvmVue构造函数里面加上Compile的初始化

// compile.js
function Compile(el, vm) {
    var _self = this
    _self._vm = vm // 保存MvvmVue实例对象
}
// 判断传过来的是否是node节点
Compile.prototype.isElement = function (node){
    return node.nodeType == 1 // 如果是1的话就是元素
}


// dataProxy.js
function MvvmVue(options) {
    this.$options = options // 得到传过来的配置
    var data = this._data = this.$options.data // 得到配置里面的data对象
    var _self = this // 保存this对象
    // 遍历属性对象JSON
    Object.keys(data).forEach(function (key) {
        // 实现属性代理
        _self._proxy(key)
    })
    // 编译HTML模板
    this.$compile = new Compile(_self.$options.el || document.body, _self)
}

判断是否是DOM节点,并使用fragment提升性能,不直接去操作DOM

// compile.js
function Compile(el, vm) {
    var _self = this
    _self._vm = vm // 保存MvvmVue实例对象
    // 得到element,
    _self._el = _self.isElement(el) ? el:document.querySelector(el)

    if(_self._el){
        // 通过得到的element去遍历所有的子元素并添加到_fragment片段
        _self._fragment =  _self.getFragment(_self._el)
        
        ... // 进行_fragment里面的模板编译
        
        // 最后一步
        // 把编译好的fragment片段添加到对应的DOM节点中
        _self._el.appendChild(_self._fragment)
    }
}
// 判创建fragment片段
Compile.prototype.getFragment=function (el){
    var fragment = document.createDocumentFragment()
    var child
    // 添加子节点到fragment
    while (child=el.firstChild) {
        fragment.appendChild(child)
    }
    return fragment
}
...

接下来就是激动人心的模板解析函数,已经把开始和结束部分写完了,就差这中间的模板解析部分。
新增一个compileFrag函数,然后把进行了fragment化的DOM做参数传递过去

function Compile(el, vm) {
    var _self = this
    _self._vm = vm // 保存MvvmVue实例对象
    // 得到element
    _self._el = _self.isElement(el) ? el:document.querySelector(el)

    if(_self._el){
        // 通过得到的element去遍历所有的子元素并添加到_fragment片段
        _self._fragment =  _self.getFragment(_self._el)
        // 进行_fragment里面的模板编译
        _self.compileFrag(_self._fragment)
        // 把编译好的fragment片段添加到对应的DOM节点中
        _self._el.appendChild(_self._fragment)
    }
}

// 模板编译函数
Compile.prototype.compileFrag=function (el){
...
}

接下来就是对所有子节点的分析,需要对节点类型进行判断,目只考虑文本类型,由于childNodes是伪数组需要转换成数组才能用到数组的一些函数,然后使用正则表达式去匹配的文本是否符合我们的要求

...
// 进行模板编译
Compile.prototype.compileFrag=function (el){
    // 拿到节点中的第一层所有子节点
    var childNodes = el.childNodes
    var _self = this
    Array.prototype.slice.apply(childNodes).forEach(function (node) {
        // 正则找 {{}} 需要拿到里面的字符串
        var reg = /\{\{(.*)\}\}/
        // 判断是否是文本且节点的内容能否匹配上正则
        if(_self.isText(node) && reg.test(node.textContent)){ 
            // 对双括号模板进行解析和编译
            ...
        }
        // 如果子节点还有子节点,就再次调用
        if (node.childNodes && node.childNodes.length) {
            // 调用实现所有层次节点的编译
            _self.compileFrag(node); // 由于需要拿到所有的子节点,所以需要用到递归调用
        }
    })
}
// 判断传过来的是否是文本
Compile.prototype.isText = function (node){
    return node.nodeType == 3 // 如果是3的话就是文本
}
...

对双括号的解析另外新增一个compileText 函数,专门来处理文本模板。
然后再新增一个内部工具函数vUtil 来处理数据和指令的编译,但目前仅有的功能是获取data中的数据

Compile.prototype.compileFrag=function (el){
    // 拿到节点中的第一层所有子节点
    var childNodes = el.childNodes
    var _self = this
    Array.prototype.slice.apply(childNodes).forEach(function (node) {
        // 正则找 {{}} 需要拿到里面的字符串
        var reg = /\{\{(.*)\}\}/
        // 判断是否是文本且节点的内容能否匹配上正则
        if(_self.isText(node) && reg.test(node.textContent)){ 
            // 对双括号模板赋值
            _self.compileText(node, RegExp.$1)
        }
        // 如果子节点还有子节点
        if (node.childNodes && node.childNodes.length) {
            // 调用实现所有层次节点的编译
            _self.compileFrag(node); // 由于需要拿到所有的子节点,所以需要用到递归调用
        }
    })
}
// 对文本节点进行赋值
// node就是含有模板的那个节点
// regStr就是在{{}}里定义的属性名
Compile.prototype.compileText = function (node, regStr){
    // 这里需要去获取在MvvmVue中所定义的data
    // 定义一个工具对象来获取具体的数据
    node.textContent = this.vUtil.getDataValue(this._vm, regStr)
}
//工具函数--之后对于模板的数据处理都会用到这个函数
Compile.prototype.vUtil = {
    // 得到{{}}中指定的数据,从实例中得到
    getDataValue: function (vm, regStr){
        var val = vm._data
        // 这里考虑到模板中可能出现a.b.c的获取方式,所以需要把所有的对象通过这种方式来获取到最终的值。
        // 其实在编码最开始的时候是不会考虑这么多的,为了节省篇幅我就先把这坑给填了
        regStr = regStr.split('.')
        regStr.forEach(function (k) {
            val = val[k]
        })
        return val
    },
}

这里说一下关于正则RegExp对象,在正则中有个叫子匹配的概念,即正则表达式中每个小括号内的部分表达式就是一个子表达式,RegExp.$1...$9属性用于返回正则表达式模式中某个子表达式匹配的文本。这里的RegExp是全局对象,RegExp.$1...$9是全局属性。当执行任意正则表达式匹配操作时,JavaScript会自动更新全局对象RegExp上的全局属性,用以存储此次正则表达式模式的匹配结果。当再次执行正则表达式匹配时,RegExp上的全局属性又会更新,覆盖掉之前的数据,以反映本次正则表达式模式的匹配结果。

这样大括号解析就算是解析成功了。

如下图所示

6.png

其他文章导航:

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

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

推荐阅读更多精彩内容

  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,231评论 0 4
  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,171评论 0 3
  •   引用类型的值(对象)是引用类型的一个实例。   在 ECMAscript 中,引用类型是一种数据结构,用于将数...
    霜天晓阅读 1,046评论 0 1
  • JavaScript语言精粹 前言 约定:=> 表示参考相关文章或书籍; JS是JavaScript的缩写。 本书...
    微笑的AK47阅读 579评论 0 3
  • 【0302我在悦读】 1098 邱邱 书名:怪诞心理学① 作者:理查德·怀斯曼 篇目:第一章 收获:01给人们一个...
    雪晴_雪晴阅读 71评论 0 0