Vue.js 源码分析(十七) 指令篇 v-if、v-else-if和v-else 指令详解
v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回true值的时候被渲染。
v-else-if,顾名思义,充当 v-if 的“else-if 块”,可以连续使用:
也可以使用 v-else 指令来表示 v-if 的“else 块”:
挺好理解的,就和大多数的语言的if()....else if()...else逻辑语句是一样的,例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
</head>
<body>
<script>
Vue.config.productionTip=false;
Vue.config.devtools=false;
</script>
<div id="app">
<p v-if="no<0">n小于0</p>
<p v-else-if="no==0">no等于0</p>
<p v-else>no大于0</p>
</div>
<script>var app = new Vue({el:'#app',data:{no:2}})</script>
</body>
</html>
writer by:大沙漠 QQ:22969969
渲染为:

有两个注意点:
v-else和v-else-if 必须紧跟在带 v-if 或者 v-else-if 的元素之后,一会儿将源码的时候会讲到为什么
因为 v-if 是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 <template> 元素当做不可见的包裹元素,
源码分析
Vue内部会把v-if、v-else、v-else-if解析称为一个三元运算符,如果有多个v-else,则三元运算符内再嵌套一个三元运算符,以例子里的为例:
解析模板解析到<p v-if="no<0">n小于0</p>这个DOM元素时会执行到processIf()函数
function processIf (el) { //第9402行 解析v-if指令
var exp = getAndRemoveAttr(el, 'v-if'); //获取表达式,例如:"no<0"
if (exp) { //如果存在v-if属性
el.if = exp; //增加if属性
addIfCondition(el, { //调用addIfCondition()函数给el增加一个ifConditions属性,值是一个对象,其中 exp表示当前v-if的值,block是当前AST对象的引用(一会儿给v-else和v-else-if用的)
exp: exp,
block: el
});
} else { //如果不存在v-if属性
if (getAndRemoveAttr(el, 'v-else') != null) { //如果存在else命令,则在el.else上增加一个else属性
el.else = true;
}
var elseif = getAndRemoveAttr(el, 'v-else-if'); //如果存在v-else-if指令,则添加elseif属性
if (elseif) {
el.elseif = elseif;
}
}
}
function addIfCondition (el, condition) { //第9453行 增加一个ifConditions属性,
if (!el.ifConditions) {
el.ifConditions = []; //如果ifConditions属性不存在则初始化为一个空数组
}
el.ifConditions.push(condition); //将参数condition这个对象push进来
}
对于v-if节点只是增加一个if和ifConditions属性,对于<p v-if="no<0">n小于0</p>来说,对应的AST对象增加的属性如下:

解析模板时,对于v-else和v-else-if来说,并没有把当前对应的AST对象加到AST树中,而是把自己对应的AST对象添加到最近的v-if的ifConditions里,代码如下:
if (currentParent && !element.forbidden) { //第9223行 如果当前对象不是根对象, 且不是style和text/javascript类型script标签
if (element.elseif || element.else) { //如果有elseif或else指令存在(设置了v-else或v-elseif指令)
processIfConditions(element, currentParent); //则调用processIfConditions()函数
} else if (element.slotScope) { // scoped slot //如果element是作用域插槽
currentParent.plain = false;
var name = element.slotTarget || '"default"';(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element;
} else {
currentParent.children.push(element);
element.parent = currentParent;
}
}
processIfConditions会在之前的AST节点,也就是v-if的AST节点的ifConditions上把当前的ast对象添加进去,如下:
function processIfConditions (el, parent) { //第9421行 解析v-else、v-else-if指令
var prev = findPrevElement(parent.children); //调用findPrevElement获取el之前的AST对象(只查找普通元素AST)
if (prev && prev.if) { //如果prev存在,且它含有v-if指令
addIfCondition(prev, { //则调用addIfCondition给prev的ifConditions添加一条语句
exp: el.elseif,
block: el
});
} else {
warn$2(
"v-" + (el.elseif ? ('else-if="' + el.elseif + '"') : 'else') + " " +
"used on element <" + (el.tag) + "> without corresponding v-if."
);
}
}
function findPrevElement (children) { //第9436行 查找children前一个文本AST对象
var i = children.length;
while (i--) { //遍历children,从后开始
if (children[i].type === 1) { //如果是普通节点
return children[i] //则直接返回该元素
} else {
if ("development" !== 'production' && children[i].text !== ' ') { //开发模式下,如果该节点不是普通节点,则报错
warn$2(
"text \"" + (children[i].text.trim()) + "\" between v-if and v-else(-if) " +
"will be ignored."
);
}
children.pop();
}
}
}
执行完后整个AST对象树如下:

接下来执行generate生成rendre函数时时发现有有if属性就执行genIf()函数:
function genIf ( //第10205行 //渲染v-if指令
el,
state,
altGen,
altEmpty
) {
el.ifProcessed = true; // avoid recursion //避免递归
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty) //调用genIfConditions函数
} function genIfConditions ( //第10215行 拼凑if表达式 conditions:比如:[{exp: "ok", block: {…}}]
conditions,
state,
altGen,
altEmpty
) {
if (!conditions.length) { //如果conditions不存在
return altEmpty || '_e()' //则直接返回altEmpty
} var condition = conditions.shift(); //获取内容,比如:{exp: "no<0", block: {…}}
if (condition.exp) { //拼凑三元运算符
return ("(" + (condition.exp) + ")?" + (genTernaryExp(condition.block)) + ":" + (genIfConditions(conditions, state, altGen, altEmpty)))
} else {
return ("" + (genTernaryExp(condition.block)))
} // v-if with v-once should generate code like (a)?_m(0):_m(1)
function genTernaryExp (el) { //再次调用genElement()函数
return altGen
? altGen(el, state)
: el.once
? genOnce(el, state)
: genElement(el, state)
}
}
最后渲染的render函数为:
_c('div',{attrs:{"id":"app"}},[(no<0)?_c('p',[_v("n小于0")]):(no==0)?_c('p',[_v("no等于0")]):_c('p',[_v("no大于0")])])
其中
(no<0)?_c('p',[_v("n小于0")]):(no==0)?_c('p',[_v("no等于0")]):_c('p',[_v("no大于0")])
就是对应的例子里的v-if、v-else、v-else-if结构了
Vue.js 源码分析(十七) 指令篇 v-if、v-else-if和v-else 指令详解的更多相关文章
- Vue.js 源码分析(十一) 基础篇 过滤器 filters属性详解
Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化.过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持).过滤器应该被添加在 JavaScrip ...
- Vue.js 源码分析(十) 基础篇 ref属性详解
ref 被用来给元素或子组件注册引用信息.引用信息将会注册在父组件的 $refs 对象上.如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素:如果用在子组件上,引用就指向组件实例,例如: ...
- Vue.js 源码分析(四) 基础篇 响应式原理 data属性
官网对data属性的介绍如下: 意思就是:data保存着Vue实例里用到的数据,Vue会修改data里的每个属性的访问控制器属性,当访问每个属性时会访问对应的get方法,修改属性时会执行对应的set方 ...
- Vue.js 源码分析(三) 基础篇 模板渲染 el、emplate、render属性详解
Vue有三个属性和模板有关,官网上是这样解释的: el ;提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标 template ;一个字符串模板作为 Vue 实例的标识使用.模板将会 ...
- Vue.js 源码分析(二) 基础篇 全局配置
Vue.config是一个对象,包含Vue的全局配置,可以在启动应用之前修改下列属性,如下: ptionMergeStrategies ;自定义合并策略的选项silent ...
- Vue.js 源码分析(九) 基础篇 生命周期详解
先来看看官网的介绍: 主要有八个生命周期,分别是: beforeCreate.created.beforeMount.mounted.beforeupdate.updated .beforeDes ...
- Vue.js 源码分析(八) 基础篇 依赖注入 provide/inject组合详解
先来看看官网的介绍: 简单的说,当组件的引入层次过多,我们的子孙组件想要获取祖先组件的资源,那么怎么办呢,总不能一直取父级往上吧,而且这样代码结构容易混乱.这个就是这对选项要干的事情 provide和 ...
- Vue.js 源码分析(七) 基础篇 侦听器 watch属性详解
先来看看官网的介绍: 官网介绍的很好理解了,也就是监听一个数据的变化,当该数据变化时执行我们的watch方法,watch选项是一个对象,键为需要观察的数据名,值为一个表达式(函数),还可以是一个对象, ...
- Vue.js 源码分析(十三) 基础篇 组件 props属性详解
父组件通过props属性向子组件传递数据,定义组件的时候可以定义一个props属性,值可以是一个字符串数组或一个对象. 例如: <!DOCTYPE html> <html lang= ...
随机推荐
- ubuntu 16.04无法连接网络;双系统无法上网;连接已断开,你现在处于断开状态
先描述一一下我的问题,若和你的一样,请继续往下看. 我是在原有Windows7系统的台式计算机中安装了ubuntu 16.04,所以目前这台计算机是双系统.打开Windows系统时有线网络正常链接.但 ...
- 【mysql】Mysql5.7--sys_schema视图
前言: MySQL 5.7中引入了一个新的sys schema,sys是一个MySQL自带的系统库,在安装MySQL 5.7以后的版本,使用mysqld进行初始化时,会自动创建sys库. sys库里面 ...
- 关于python的十一道练习
关于python的十一道练习 1.编写程序,输入一个自然数字符串,然后输出各位数字之和.例如,输入字符串1234,输出10. def sums1(): #第一题 strs=input('请输入一个自然 ...
- Spring源码解析系列汇总
相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ...
- Javase之多线程(2)
多线程(2) 线程的生命周期 新建:创建线程对象 就绪:有执行资格,没有执行权 运行:有资格运行,有执行权 阻塞:由一些操作让线程处于改状态.没有执行资格,没有执行权,而通过另一些操作激活它,激活 ...
- WeTest全球化服务,为使命召唤手游质量保驾护航
导读 使命召唤系列作为经典FPS游戏,以良好的表现与出色的射击手感,颠覆了玩家对传统第一人称射击的传统观念.同名手游(CODM)10月份在海外上线,仅一周内下载量就已突破一亿次,更是横扫139个国家及 ...
- 0基础入门学习Python(第5章)
列表,元组和字符串 5.1 列表:一个打了激素的数组 有时候可能需要将一些相互之间有关联的数据保存在一起,这个就叫数组.Python将其称为列表. 5.1.1 创建列表 >>> [1 ...
- [转]Outlook API
本文转自:https://www.cnblogs.com/yl153/p/6711519.html 1.Outlook简介 若要从Outlook 外控制Outlook对象,必须在编写代码的工程中建立对 ...
- 微信小程序动画之弹出菜单
用微信小程序做了一个动画,效果如上图: 代码: js: Page({ data: { isPopping: false, animPlus: {}, animCollect: {}, animTran ...
- spark的wordcount
在开发环境下实现第一个程序wordcount 1.下载和配置scala,注意不要下载2.13,在spark-core明确支持scala2.13前,使用2.12或者2.11比较好. https://ww ...