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. Angular 18+ 高级教程 – Component 组件 の Attribute Directives 属性型指令

    介绍 指令就是没有模板的组件.除了模板其它的都有,比如 selector.inject.@Input.lifecycle 等等. 那既然都有完整的组件了,为什么还搞一个少掉模板的指令呢? 很简单啊,因 ...

  2. CSS – border-radius (Rounded Corners)

    前言 之前的文章 CSS – W3Schools 学习笔记 (3), 这篇独立出来写, 作为整理. 参考: Youtube – Advanced CSS Border-Radius Tutorial ...

  3. QT与JavaScript之QT6应用程序与JavaScript脚本集成开发:C++应用程序能与JavaScript实现相互调用吗?

    简介 QT6框架中提供了JavaScript引擎类型QJSEngine ,可用于实现 C++应用程序和JavaScript代码之间的相互调用. 目录 什么是 ECMAScript ? JavaScri ...

  4. 4.1 数列的概念2 (递推公式、前n项和)

    \({\color{Red}{欢迎到学科网下载资料学习 }}\) [ [基础过关系列]高二数学同步精品讲义与分层练习(人教A版2019)] ( https://www.zxxk.com/docpack ...

  5. manim边学边做--无向图

    无向图属于数学中的图论这一学科, 所谓无向图G,就是由顶点集V(非空集合)和边集E(由V中元素构成的无序二元组的集合)组成的图, 可表示为G=(V,E). 在无向图中,边没有方向,即从顶点A到顶点B的 ...

  6. iOS长按手势列表拖拽功能实现

    项目开发中遇到拖拽功能的需求,具体要求是在编辑状态下,首页底部菜单项可以拖动位置,便于位置切换.遇到问题后的初步想法是添加拖拽手势,拖拽到某个位置,判断拖拽cell的中心点是否在另一个cell内,这样 ...

  7. html5新标签 画布 canvas 替代了 flash

    绘制矩形边框,和填充不同的是绘制使用的是strokeRect, 和strokeStyle实现的 绘制路径 绘制路径的作用是为了设置一个不规则的多边形状态 路径都是闭合的,使用路径进行绘制的时候需要既定 ...

  8. style 标签写在body 前后的区别?

    知识储备:了解浏览器渲染页面的流程 a)首先 , 解析(parse)html 标签 , 获取DOM 树 b)解析html 的同时 , 解析css  , 获得样式规则 (style rules) CSS ...

  9. 形参和实参的区别 and 联系

    联系:形参和实参是赋值关系,是按照值传递的,简单数据直接传递值,形参和实参的改变互不影响,因为形参和实参是两个变量,是两个数据(栈内存): 引用数据传递的是地址,结果就是一个数据,两个地址同时指向一个 ...

  10. 自定义指令 v-imgerror 当图片的 src 资源 无效 就替换 默认的 src 显示图片

    // 回顾自定义指令 // 作用 : 自定义一些对dom操作的快捷指令 // 前提:指令就是用来操作 dom (v-if /v-show/v-for ....) // 语法:Vue.directive ...