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. Jmeter、postman、python 三大主流技术如何操作数据库?

    1.前言 只要是做测试工作的,必然会接触到数据库,数据库在工作中的主要应用场景包括但不限于以下: 功能测试中,涉及数据展示功能,需查库校验数据正确及完整性:例如商品搜索功能 自动化测试或性能测试中,某 ...

  2. HTTP——请求数据格式

    请求数据格式   

  3. 了解 Flutter 3.16 功能更新

    作者 / Kevin Chisholm 我们在季度 Flutter 稳定版发布会上带来了 Flutter 3.16,此版本包含诸多更新: Material 3 成为新的默认主题.为 Android 带 ...

  4. [OI] 数学与推论证明 3(高中数学篇)

    1 \[\color{#40865d}(2) \] \(f(x)=x^{2}-a(x+a\ln x)(a\neq0)\),若 \(f(1)+f'(1)=0\) 且 \(a\gt 0\),问可以得到什么 ...

  5. 掌握Docker:简化KES单机安装与管理的最佳实践

    今天我们将继续深入探讨KES的单机安装,依然围绕Docker的使用展开.这一部分的内容将涵盖一些常见的陷阱以及在遇到问题时如何进行有效的反馈和解决.首先,我们需要找到官方的安装教程,确保以官方指南为主 ...

  6. 八字测算api接口数据示例_奥顺八字测算接口分享

    八字测算免费api接口,每日开放时间在早上8点到晚上十点,本api接口完全免费,是奥顺居八字测算网程序内部接口,提供本地调用的,现在免费分享出来,仅供测试. 接口名称:八字精批测算api接口示例 接口 ...

  7. 将nii文件CT图像更改窗宽窗位之后保存成nii文件

    因为项目需要把CT图像中骨头更加明确的显示出来,且还需要保存nii文件,所以查了一些资料,在这里做一下笔记,方便以后使用.代码如下: import nibabel as nib import nump ...

  8. 使用 Debugger 断点 如果打开了断点调试 就会跳转空白页面

    <!DOCTYPE html> <html> <header> <title>test</title> </header> &l ...

  9. 66.有没有碰到过数组响应丢失(问的是ref和reactive的用法,什么情况下用)

    由于vue3使用proxy,对于对象和数组都不能直接整个赋值.  直接赋值丢失了响应性 只有push或者根据索引遍历赋值才可以保留reactive数组的响应性  : 可以使用 toRefs 解决这个问 ...

  10. 57.dom递归退出循环的时机

    递归的终止条件一般定义在递归函数内部,在递归调用前要做一个条件判断,根据判断的结果选择是继续调用自身,还是return:返回终止递归. 终止的条件: 1.判断递归的次数是否达到某一限定值 2.判断运算 ...