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 ...
随机推荐
- HTML & CSS – 实战 RWD Image 响应式图片
前言 之前写过 HTML & CSS – Responsive Image 响应式图片 (完整版), 里面解释了原理和一些具体做法, 但是并不是以真实场景作为例子带入. 由于 RWD Imag ...
- CSS – BEM (Block__Element--Modifier)
前言 BEM 是一种 CSS class 命名规范. 目的是防止大家名字取太短, 不小撞名字后果很麻烦. 参考: Youtube – You Probably Need BEM CSS in Your ...
- Flutter Forward 活动正式发布
2023 年 1 月 25 日,Flutter 团队将在肯尼亚首都内罗毕举办 Flutter Forward 大会,并同时开启线上直播,敬请期待! 活动将于北京时间 1 月 25 日 22:30 开始 ...
- 系统编程-进程-当fork遇到管道,可能碰撞出什么?
第一部分 1. 直接上代码 #include <stdio.h> #include <unistd.h> #include <stdlib.h> int globv ...
- 【赵渝强老师】NoSQL数据库之Cassandra基础
一.Cassandra简介 Cassandra是一个混合型的非关系的数据库,类似于Google的BigTable.其主要功能比Dynamo (分布式的Key-Value存储系统)更丰富,但支持度却不如 ...
- `std::packaged_task`、`std::thread` 和 `std::async` 的区别与联系
std::packaged_task.std::thread 和 std::async 的区别与联系 std::packaged_task.std::thread 和 std::async 都是 C+ ...
- 使用iis设置网站php版本为7.3
内容:使用iis设置网站php版本为7.3这张图 是多少人的噩梦 早期的宝塔版本 没办法在线升级, php版本只能到7.1 默认就没有7.2以上版本 怎么办?可以在iis设置第一步: 第二步 ...
- OpenCL架构
OpenCL提供了一种统一的编程接口,使得程序员可以编写一次代码,然后在多种处理器上运行. 平台模型 OpenCL平台总是包括一个宿主机(host).宿主机与OpenCL程序外部的环境交互,包括I/O ...
- js正则表达式 禁止输入汉字
const validateChinese = (rule, value, callback) => { var regex = /[\u4e00-\u9fa5]/; console.log(' ...
- excel导⼊功能的实现流程简要描述⼀下?
当时公司的场景⼤概⼀个excel⽂件⾥就⼏⼗条数据,量⽐较少,和后端商量之后制定了前端主导的⽅ 案,解析的过程放到了浏览器端做,当时是参考了⼀下vue-admin中的现成的⽅案 ⼤概流程是这样的,⼈事 ...