1.大括号表达式

(1)在MVVM()中接收并保存配置对象

(2)调用Compile编译函数,将el和vm传入

function MVVM (option) {
this.$option = option || {}
this._data = option.data
// 调用编译函数
new Compile(option.el || document.body, this)
}

(3)在Compile()中保存vm,并根据el获取对应的dom,将这个dom元素的所有子节点移动到fragment中

(4)遍历fragment中的所有子节点,使用compileElement()编译此节点

(5)如果子节点是文本节点且匹配到{{}}格式的文本,则将{{}}替换成对应的值,如果该节点存在子节点,则递归调用compileElement()编译此节点

(6)最后将将编译好的fragment插入到el对应的dom中

function Compile (el,vm) {
this.$vm = vm
this.$el = document.querySelector(el) // 如果这个dom存在
if(this.$el) {
// 则将其所有子节点移动到fragment中
this.$fragment = this.nodeToFragment(this.$el)
// 调用初始化函数,编译fragment
this.init()
// 将编译好的fragment插入到el中
this.$el.appendChild(this.$fragment)
}
}
Compile.prototype = {
nodeToFragment (el) {
//创建fragment对象
var fragment = document.createDocumentFragment()
var child
while(child = el.firstChild) {
// 将原生节点移动到fragment中
fragment.appendChild(child)
} // 返回fragment
return fragment;
}, init: function() {
// 调用编译某个节点的函数
this.compileElement(this.$fragment);
}, // 这个函数用来编译传入元素的子节点,且会被递归调用
compileElement (el) {
var me = this
// 获取所有子节点
var childNodes = el.childNodes
// 遍历所有子节点
Array.prototype.slice.call(childNodes).forEach(node => {
// 创建匹配{{}}格式的正则
// 禁止贪婪{{xxx}}--{{ddd}} => xxx 和 ddd(会匹配到2个)
// 如果不禁止贪婪 就会变成{{xxx}}--{{ddd}} => xxx}}--{{ddd
var reg = /\{\{(.*?)\}\}/ // 使用循环将此节点的所有{{xxx}}依次替换成vm._data对应的值
while(node.nodeType == 3 && reg.test(node.textContent)) {
// 获取{{}}中变量在vm.data中对应的值 RegExp.$1就是第一个匹配的值
var val = me.getVMVal(this.$vm, RegExp.$1.trim())
// 获取原始的值
var oldVal = node.textContent
// 用vm.data中对应的值将{{xxx}}替换掉
node.textContent = oldVal.replace(reg,val)
}
// 如果该节点存在 且 有子节点 则调用递归 编译此节点
if(node.childNodes && node.childNodes.length) {
me.compileElement(node)
}
})
}, // 获取变量在vm.data中对应的值
getVMVal (vm,exp) {
var val = vm._data
// exp值可能是a.b.c
expArr = exp.split('.')
expArr.forEach(function(key){
val = val[key]
})
return val
}
}
<!-- html模版 -->
<div id="app">
<p>名字:{{person.name}} -- 年纪:{{person.age}}</p>
</div>
//实例化
var vm = new MVVM({
el:'#app',
data: {
person:{
name: '子龙',
age: 20
}
}
})

渲染结果:

<div id="app">
<p>名字:子龙 -- 年纪:20</p>
</div>

2.解析v-html/v-on指令

(1)在compileElement()中遍历节点时,如果此节点是元素节点,则调用compileOrder()编译此节点的属性节点(只有元素节点才有可能存在属性节点)

(2)compileOrder()中遍历该元素节点的所有属性节点,如果属性名是符合定义的指令,则根据指令类型进行相应的操作

(3)如果是v-html指令,则操作该元素节点的innerHTML属性

(4)如果是v-on指令,则为该节点添加事件监听

(5)指令编译完成之后移除指令

Compile.prototype = {
nodeToFragment (el) {
//创建fragment对象
var fragment = document.createDocumentFragment()
var child
while(child = el.firstChild) {
// 将原生节点移动到fragment中
fragment.appendChild(child)
} // 返回fragment
return fragment;
}, init: function() {
// 调用编译某个节点的函数
this.compileElement(this.$fragment);
}, // 这个函数用来编译传入元素的子节点,且会被递归调用
compileElement (el) {
// 获取所有子节点
var childNodes = el.childNodes
// 遍历所有子节点
Array.prototype.slice.call(childNodes).forEach(node => {
// 创建匹配{{}}格式的正则
// 禁止贪婪{{xxx}}--{{ddd}} => xxx 和 ddd(会匹配到2个)
// 如果不禁止贪婪 就会变成{{xxx}}--{{ddd}} => xxx}}--{{ddd
var reg = /\{\{(.*?)\}\}/ // 如果该节点是 元素节点
if(node.nodeType === 1){
// 编译此元素属性中的指令
this.compileOrder(node)
}else{
// 使用循环将此节点的所有{{xxx}}依次替换成vm._data对应的值
while(node.nodeType == 3 && reg.test(node.textContent)) {
// 获取{{}}中变量在vm.data中对应的值 RegExp.$1就是第一个匹配的值
var val = this.getVMVal(this.$vm, RegExp.$1.trim())
// 获取原始的值
var oldVal = node.textContent
// 用vm.data中对应的值替换 {{xxx}}
node.textContent = oldVal.replace(reg,val)
}
}
// 如果该节点存在 且 有子节点 则调用递归 编译此节点
if(node.childNodes && node.childNodes.length) {
this.compileElement(node)
}
})
}, // 获取变量在vm.data中对应的值
getVMVal (vm,exp) {
var val = vm._data
// exp值可能是a.b.c
expArr = exp.split('.')
expArr.forEach(function(key){
val = val[key]
})
return val
},
compileOrder: function(node){
// 获取该节点所有属性节点
var nodeAttrs = node.attributes
// 遍历所有属性
Array.from(nodeAttrs).forEach(attr => {
// 获取属性名
var attrName = attr.name
// 判断属性是否是我们自定的指令
if(this.isDirective(attrName)){
// 获取指令对应的表达式
var exp = attr.value
// 获取指令 v-text => text (截去前两个字符)
var dir = attrName.substring(2)
// 判断指令类型 是否是事件指令
if(this.isEventDirective(dir)){
// 调用指令处理对象的相应方法 dir == on:click
compileUtil.eventHandler(node,dir,exp,this.$vm)
}else {
// 普通指令 v-text
compileUtil[dir] && compileUtil[dir](node,this.getVMVal(this.$vm, exp))
}
// 指令编译完成之后移除指令
node.removeAttribute(attrName)
}
})
},
isDirective: function(attrName){
// 只有 v- 开头的属性名才是我们定义的指令
return attrName.indexOf('v-') == 0
// attrName.startsWith("v-")
},
isEventDirective: function(dir){
// 事件指令以 on 开头
return dir.indexOf('on') == 0
}
} // 指令处理集合
var compileUtil = {
text: function(node,value){
node.textContent = typeof value == 'undefined' ? '' : value
},
html: function(node,value){
node.innerHTML = typeof value == 'undefined' ? '' : value
},
eventHandler: function(node,dir,exp,vm){
// 为节点绑定事件 (哪个节点,哪个事件,触发哪个回调) // 获取事件名称 on:click => click
var eventName = dir.split(':')[1]
// 根据exp获取其在在vm中对应的函数
var fn = vm.$option.methods[exp] // 只有事件名称和回调同时存在才添加事件监听
if(eventName && fn){
// 回调函数强制绑定this为vm
node.addEventListener(eventName,fn.bind(vm),false)
}
}
}
<div id="app">
<div v-html="html"></div>
<div v-text="html"></div>
<button v-on:click="test">点我</button>
</div>
var vm = new MVVM({
el:'#app',
data: {
html:"<h3>我是h3</h3>",
},
methods:{
test(){
console.log('click test')
}
}
})

Vue 模版解析的更多相关文章

  1. 不使用 vue-cli 与 vue 模版,使用 Vue2.x + webpack4.x 从零开始一步步搭建项目框架

    说明 这是我根据慕课网上的一个课程 Vue+Webpack打造todo应用 过程一步步搭下来的框架,去掉了业务相关的逻辑. 项目最终的效果包括了引入vue框架:使用CSS预处理器:使用babel:引用 ...

  2. js模版解析

    function JzRender(tpl, data) { // 模版解析 data是对象则返回字符串,是数组则返回字符串数组 if (data instanceof Array) { var s ...

  3. vscode vue 模版生成,vue 一键生成

    vscode vue 模版 继上篇文章(vue 格式化),顺便记录下 vue 模版生成.图片就不在贴了,如果有找不到 vscode 插件商店的可以访问上篇文章. 一.安装 VueHelper 在 vs ...

  4. VSCode 初次写vue项目并一键生成.vue模版

    VSCode 写vue项目一键生成.vue模版 1.新建代码片段 文件-->首选项-->用户代码片段-->点击新建代码片段--取名vue.json 确定 2.配置快捷生成的vue模板 ...

  5. 自己用的框架写了一个PHP模版解析类

    <?php if(!defined('IS_HEARTPHP')) exit('Access Denied'); /** * template.class.php 模板解析类 * * @copy ...

  6. 定制自己的vue模版

    前言 使用vue这个框架已经陆陆续续做了好几个项目了,不知不觉也已经在公司呆了4个月,转正了.时间如水...(省略一万字)./咳-不瞎扯了,公司是直接用的官方脚手架生成项目,官方模版没有vuex,ax ...

  7. Ant Design Vue项目解析-前言

    源码系列文章很长时间没有更新,一是在考虑文章用什么方式写质量会更高,用什么方式总结更易于扩展和总结知识点,加上工作.看书.健身占用的时间比较多所以也没时间去整理.最近在网上看到一篇文章感觉这种方式不错 ...

  8. 用Scriban进行模版解析

    前言 有些时候,我们需要根据模版去展示一些内容,通常会借助模版引擎来处理. 举个简单的例子,发短信. 短信肯定是有模版的,不同的场景对应不同的模版. 注册的, [xxx]恭喜您成功注册yyy平台,您的 ...

  9. vue如何解析xml文件 x2js

    好久没来写东西了,主要是一直在加班,哼哼,不开心 项目中会用到将xml文件解析成json文件在页面中显示出来,以前jq的时候用到的方法行不通了,故在这边介绍一种我觉得还不错的插件 1. npm安装 n ...

  10. [转] Vue原理解析——自己写个Vue

    一.Vue对比其他框架原理 Vue相对于React,Angular更加综合一点.AngularJS则使用了“脏值检测”. React则采用避免直接操作DOM的虚拟dom树.而Vue则采用的是 Obje ...

随机推荐

  1. SQL Server – 基本操作 Table 和 Column

    前言 日常都是用 EF Core 来管理 Database, 偶尔也用 Management Studio, 就是很少手写 Command. 虽然网上一拉就有很多, 但是每次写单侧都要到处找还是挺烦的 ...

  2. 强!推荐一款Python开源自动化脚本工具:AutoKey!

    1.前言 在数字化时代,自动化工具成为了提升工作效率和生产力的重要手段.Python作为一种广泛使用的编程语言,以其强大的功能和易用性受到许多开发者的青睐. 而今天给大家推荐一款开源的自动化脚本工具: ...

  3. 邀请参与第四季度 Flutter 开发者调查

    本次调研会较为详细地了解大家对 Flutter 框架.开发语言.package 生态 (包括 Firebase package).IDE 插件以及 Material Design / Cupertin ...

  4. Kubernetes Deployment控制器(二十)

    前面我们学习了 ReplicaSet 控制器,了解到该控制器是用来维护集群中运行的 Pod 数量的,但是往往在实际操作的时候,我们反而不会去直接使用 RS,而是会使用更上层的控制器,比如我们今天要学习 ...

  5. 阿里邮箱网页正常登陆,outlook报错

    事件起因: 某客户使用阿里邮箱办公,然又使用outlook绑定阿里邮箱:在网页端可以登录阿里邮箱,但是在outlook的登录的时候,服务器.端口均设置无误,但是就是登录不上去,死活都等登录不上去,总是 ...

  6. 北京智和信通 | 无人值守的IDC机房动环综合监控运维

    随着信息技术的发展和全面应用,数据中心机房已成为各大企事业单位维持业务正常运营的重要组成部分,网络设备.系统.业务应用数量与日俱增,规模逐渐扩大,一旦机房内的设备出现故障,将对数据处理.传输.存储以及 ...

  7. USB协议详解第6讲(USB描述符-端点描述符)

    1.USB描述符 USB描述符有设备描述符.标准配置描述符.接口描述符.端点描述符.字符串描述符,HID设备有HID描述符.报告描述符和物理描述符.今天主要是学习USB端点描述符的组成. 2.端点描述 ...

  8. iOS开发Block使用详解

    项目开发中经常会用到页面之间传值的问题,常用的方法是通知.单例.代理.block等.最近项目忙完,有空细细研究了一下block的用法,收货多多. block又称闭包,它的实现原理是c语言的函数指针.函 ...

  9. threejs - 渲染第一个3D场景 - 旋转的正方体

    1. 安装 threejs & 使用 2. 创建三要素  场景 scene 相机 camera 渲染器 render 3. 场景 new THREE.Scene() 相机分为 2 种  1. ...

  10. C# 新语法 switch 的简单写法

    // C# 中的新语法 switch 的简写 string str = "123"; string res = str switch { "1" => & ...