Vue 模版解析
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 模版解析的更多相关文章
- 不使用 vue-cli 与 vue 模版,使用 Vue2.x + webpack4.x 从零开始一步步搭建项目框架
说明 这是我根据慕课网上的一个课程 Vue+Webpack打造todo应用 过程一步步搭下来的框架,去掉了业务相关的逻辑. 项目最终的效果包括了引入vue框架:使用CSS预处理器:使用babel:引用 ...
- js模版解析
function JzRender(tpl, data) { // 模版解析 data是对象则返回字符串,是数组则返回字符串数组 if (data instanceof Array) { var s ...
- vscode vue 模版生成,vue 一键生成
vscode vue 模版 继上篇文章(vue 格式化),顺便记录下 vue 模版生成.图片就不在贴了,如果有找不到 vscode 插件商店的可以访问上篇文章. 一.安装 VueHelper 在 vs ...
- VSCode 初次写vue项目并一键生成.vue模版
VSCode 写vue项目一键生成.vue模版 1.新建代码片段 文件-->首选项-->用户代码片段-->点击新建代码片段--取名vue.json 确定 2.配置快捷生成的vue模板 ...
- 自己用的框架写了一个PHP模版解析类
<?php if(!defined('IS_HEARTPHP')) exit('Access Denied'); /** * template.class.php 模板解析类 * * @copy ...
- 定制自己的vue模版
前言 使用vue这个框架已经陆陆续续做了好几个项目了,不知不觉也已经在公司呆了4个月,转正了.时间如水...(省略一万字)./咳-不瞎扯了,公司是直接用的官方脚手架生成项目,官方模版没有vuex,ax ...
- Ant Design Vue项目解析-前言
源码系列文章很长时间没有更新,一是在考虑文章用什么方式写质量会更高,用什么方式总结更易于扩展和总结知识点,加上工作.看书.健身占用的时间比较多所以也没时间去整理.最近在网上看到一篇文章感觉这种方式不错 ...
- 用Scriban进行模版解析
前言 有些时候,我们需要根据模版去展示一些内容,通常会借助模版引擎来处理. 举个简单的例子,发短信. 短信肯定是有模版的,不同的场景对应不同的模版. 注册的, [xxx]恭喜您成功注册yyy平台,您的 ...
- vue如何解析xml文件 x2js
好久没来写东西了,主要是一直在加班,哼哼,不开心 项目中会用到将xml文件解析成json文件在页面中显示出来,以前jq的时候用到的方法行不通了,故在这边介绍一种我觉得还不错的插件 1. npm安装 n ...
- [转] Vue原理解析——自己写个Vue
一.Vue对比其他框架原理 Vue相对于React,Angular更加综合一点.AngularJS则使用了“脏值检测”. React则采用避免直接操作DOM的虚拟dom树.而Vue则采用的是 Obje ...
随机推荐
- EF Core – ExecuteUpdate and ExecuteDelete (Bulk updates 批量更新和删除)
前言 EF Core 在 SaveChanges 之后会一句一句的去更新和删除数据. 有时候这个效率是很差的. 而 SQL 本来就支持批量更新和删除, 所以是 EF Core 的缺失. 在 EF Co ...
- EF Core – Library use EF
前言 写 Library 有时候会用到 database, 会想用 EF 来维护. 比如 Identity, IdentityServer, OpenIddict, 这些 Library 都有使用到 ...
- linux 映射windows 下的共享文件夹
linux 映射windows 下的共享文件夹 本文讯]2021年4月27日 在对接第三方系统,进行数据采集的时候,对方给了我们一个文件夹,里面全是txt文件,这个时候就要想办法获取他们数据 ...
- SQL limit字句
limit用法介绍 limit子句可以返回检索查询行的某一连续的部分 用法介绍: SELECT column_list FROM table1 ORDER BY column_list LIMIT r ...
- WeiXin.Export.20211230
C# 在PC上的通过蓝牙(bluetooth)发送数据到手机 将.net framework 4 部署在docker中的全过程(支持4.0 到 4.8,3.5应该也可以) .Net Core Http ...
- form data 与request payload的区别以及php接收这些数据的方法
form data 与request payload的区别以及php接收这些数据的方法 以前与前端交互一直都是POST.GET的,PHP端就直接$_POST,$_GET来接收,从来没有出现过以外. 最 ...
- python处理nii文件
第一步安装nibabel,可以使用命令:pip install nibabel 之后: from nibabel.viewers import OrthoSlicer3D as osdimport n ...
- 墨天轮访谈 | OceanBase 白超:海量数据管理,为什么选择OceanBase?
分享嘉宾:白超(大窑) OceanBase解决方案架构师.前蚂蚁集团数据库团队DBA专家 整理:墨天轮社区 导 读 大家好,我是白超(花名:大窑),在过去的几年中,作为蚂蚁集团数据库SRE团队成员,经 ...
- 标准库之 random 模块
一.介绍random模块 1.1.random模块简介 random模块是Python标准库中用于生成伪随机数的模块,伪随机数是通过算法生成的数列,在一定范围内表现出随机性,虽然这些数列在一定程度上是 ...
- 玩玩虚拟化-KVM
1.讲在前面(玩这个的心历路程) 最近一段时间想玩一些集群之类的东西,学习搞一下K8s,集群啥的,但是我没有多台服务器,如果购买云服务器成本太高,后来想到了买台台式机弄点虚拟机来玩,于是我就在某鱼上淘 ...