Vue.js 源码分析(二十三) 指令篇 v-show指令详解
v-show的作用是将表达式值转换为布尔值,根据该布尔值的真假来显示/隐藏切换元素,它是通过切换元素的display这个css属性值来实现的,例如:
<!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>
<div id="d"><p v-show="isShow">Hello Vue!</p></div>
<script>
Vue.config.productionTip=false;
Vue.config.devtools=false;
var app = new Vue({el:'#d',data:{isShow:true}})
</script>
</body>
</html>
渲染结果为:
当我们在修改isShow为false时:
页面里的Hello Vue!就隐藏部件了,我们查看DOM结构如下:
可以看到Vue是通过修改display这个CSS属性来隐藏元素的
源码分析
在解析模板将DOM转换成AST对象的时候会执行processAttrs()函数,如下:
function processAttrs (el) { //解析Vue的属性
var list = el.attrsList;
var i, l, name, rawName, value, modifiers, isProp;
for (i = 0, l = list.length; i < l; i++) { //遍历每个属性
name = rawName = list[i].name;
value = list[i].value;
if (dirRE.test(name)) { //如果该属性以v-、@或:开头,表示这是Vue内部指令
// mark element as dynamic
el.hasBindings = true;
// modifiers
modifiers = parseModifiers(name);
if (modifiers) {
name = name.replace(modifierRE, '');
}
if (bindRE.test(name)) { // v-bind //bindRD等于/^:|^v-bind:/ ,即该属性是v-bind指令时
/*v-bind的分支*/
} else if (onRE.test(name)) { // v-on
/*v-on的分支*/
} else { // normal directives
name = name.replace(dirRE, ''); //去掉指令前缀,比如v-show执行后等于show
// parse arg
var argMatch = name.match(argRE);
var arg = argMatch && argMatch[1];
if (arg) {
name = name.slice(0, -(arg.length + 1));
}
addDirective(el, name, rawName, value, arg, modifiers); //执行addDirective给el增加一个directives属性
if ("development" !== 'production' && name === 'model') {
checkForAliasModel(el, value);
}
}
} else {
/*非Vue指令的分支*/
}
}
}
addDirective会给AST对象上增加一个directives属性保存指令信息,如下:
function addDirective ( //第6561行 指令相关,给el这个AST对象增加一个directives属性,值为该指令的信息
el,
name,
rawName,
value,
arg,
modifiers
) {
(el.directives || (el.directives = [])).push({ name: name, rawName: rawName, value: value, arg: arg, modifiers: modifiers });
el.plain = false;
}
例子里的p元素执行到这里时对应的AST对象如下:
接下来在generate生成rendre函数的时候,会执行genDirectives()函数,将AST转换成一个render函数,如下:
with(this){return _c('div',{attrs:{"id":"d"}},[_c('p',{directives:[{name:"show",rawName:"v-show",value:(isShow),expression:"isShow"}]},[_v("Hello Vue!")])])}
最后等渲染完成后会执行directives模块的create钩子函数,如下:
var directives = { //第6173行 directives模块
create: updateDirectives, //创建DOM后的钩子
update: updateDirectives,
destroy: function unbindDirectives (vnode) {
updateDirectives(vnode, emptyNode);
}
} function updateDirectives (oldVnode, vnode) { //第6181行 oldVnode:旧的Vnode,更新时才有 vnode:新的VNode
if (oldVnode.data.directives || vnode.data.directives) {
_update(oldVnode, vnode);
}
} function _update (oldVnode, vnode) { //第6187行 初始化/更新指令
var isCreate = oldVnode === emptyNode; //是否为初始化
var isDestroy = vnode === emptyNode;
var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);
var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context); //调用normalizeDirectives$1()函数规范化参数1,返回格式:{v-show:{name: "show", rawName: "v-show", value: true, expression: "ok", modifiers: {…}, …}} var dirsWithInsert = [];
var dirsWithPostpatch = []; var key, oldDir, dir;
for (key in newDirs) { //遍历newDirs
oldDir = oldDirs[key]; //oldVnode上的key指令信息
dir = newDirs[key]; //vnode上的key指令信息
if (!oldDir) { //如果oldDir不存在,即是新增指令
// new directive, bind
callHook$1(dir, 'bind', vnode, oldVnode); //调用callHook$1()函数,参数2为bind,即执行v-show指令的bind函数
if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir);
}
} else {
// existing directive, update
dir.oldValue = oldDir.value;
callHook$1(dir, 'update', vnode, oldVnode);
if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir);
}
}
}
/*以下略*/
}
normalizeDirectives$1会调用resolveAsset()函数从Vue.options.directives里获取v-show指令的信息如下:
function normalizeDirectives$1 ( //第6249行 规范化dirs
dirs,
vm
) {
var res = Object.create(null); //存储最后的结果
if (!dirs) { //如果用户没有定义指令,则直接返回空对象
// $flow-disable-line
return res
}
var i, dir;
for (i = 0; i < dirs.length; i++) { ///遍历dirs
dir = dirs[i];
if (!dir.modifiers) { //如果没有修饰符,则重置dir.modifiers为空对象
// $flow-disable-line
dir.modifiers = emptyModifiers;
}
res[getRawDirName(dir)] = dir; //将dir保存到res里面,键名为原始的指令名
dir.def = resolveAsset(vm.$options, 'directives', dir.name, true); //调用resolveAsset获取该指令的信息,是一个对象,保存到res的def属性里面
}
// $flow-disable-line
return res
}
resolveAsset是获取资源用的,当我们定义了组件、过滤器、指令时,都通过该函数获取对应的信息,之前组件和过滤里介绍了,这里不说了
回到_update函数,最后调用callHook$1()函数,参数2为bind,该函数如下:
writer by:大沙漠 QQ:22969969
function callHook$1 (dir, hook, vnode, oldVnode, isDestroy) { //第6276行 执行指令的某个回调函数 dir:指令信息,
var fn = dir.def && dir.def[hook]; //尝试获取钩子函数
if (fn) {
try {
fn(vnode.elm, dir, vnode, oldVnode, isDestroy); //执行钩子函数,参数依次为绑定的元素、dir对象、新的VNode,老的VNode
} catch (e) {
handleError(e, vnode.context, ("directive " + (dir.name) + " " + hook + " hook"));
}
}
}
v-show指令的信息如下:
var show = { //第8082行 v-show指令的信息
bind: function bind (el, ref, vnode) { //初次绑定时执行
var value = ref.value; vnode = locateNode(vnode);
var transition$$1 = vnode.data && vnode.data.transition; //尝试获取transition,如果v-show绑定的标签外层套了一个transition则会把信息保存到该对象里 这是transition的组件分支,可先忽略
var originalDisplay = el.__vOriginalDisplay = //保存最初的display属性
el.style.display === 'none' ? '' : el.style.display;
if (value && transition$$1) { //如果transition$$1存在的话
vnode.data.show = true;
enter(vnode, function () {
el.style.display = originalDisplay;
});
} else {
el.style.display = value ? originalDisplay : 'none'; //否则直接根据value的值是否可以转换为1来设置el.style.display属性
}
}, update: function update (el, ref, vnode) {
/*更新时的逻辑*/
}, unbind: function unbind (
el,
binding,
vnode,
oldVnode,
isDestroy
) {
/*卸载时的逻辑*/
}
}
v-show的流程就是这样的,注意,v-show不支持<template>元素,也不支持v-else。
Vue.js 源码分析(二十三) 指令篇 v-show指令详解的更多相关文章
- Vue.js 源码分析(二十六) 高级应用 作用域插槽 详解
普通的插槽里面的数据是在父组件里定义的,而作用域插槽里的数据是在子组件定义的. 有时候作用域插槽很有用,比如使用Element-ui表格自定义模板时就用到了作用域插槽,Element-ui定义了每个单 ...
- Vue.js 源码分析(二十八) 高级应用 transition组件 详解
transition组件可以给任何元素和组件添加进入/离开过渡,但只能给单个组件实行过渡效果(多个元素可以用transition-group组件,下一节再讲),调用该内置组件时,可以传入如下特性: n ...
- Vue.js 源码分析(二十九) 高级应用 transition-group组件 详解
对于过度动画如果要同时渲染整个列表时,可以使用transition-group组件. transition-group组件的props和transition组件类似,不同点是transition-gr ...
- Vue.js 源码分析(十四) 基础篇 组件 自定义事件详解
我们在开发组件时有时需要和父组件沟通,此时可以用自定义事件来实现 组件的事件分为自定义事件和原生事件,前者用于子组件给父组件发送消息的,后者用于在组件的根元素上直接监听一个原生事件,区别就是绑定原生事 ...
- Vue.js 源码分析(二十四) 高级应用 自定义指令详解
除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令. 官网介绍的比较抽象,显得很高大上,我个人对自定义指令的理解是:当自定义指令作用在一些DOM元素或组件上 ...
- Vue.js 源码分析(二十二) 指令篇 v-model指令详解
Vue.js提供了v-model指令用于双向数据绑定,比如在输入框上使用时,输入的内容会事实映射到绑定的数据上,绑定的数据又可以显示在页面里,数据显示的过程是自动完成的. v-model本质上不过是语 ...
- Vue.js 源码分析(二十一) 指令篇 v-pre指令详解
该指令会跳过所在元素和它的子元素的编译过程,也就是把这个节点及其子节点当作一个静态节点来处理,例如: <!DOCTYPE html> <html lang="en" ...
- Vue.js 源码分析(二十) 指令篇 v-once指令详解
数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值,例如:<p>Message: {{ msg }}</p>以后每当msg属性发生了改变,插值处的内 ...
- Vue.js 源码分析(二十七) 高级应用 异步组件 详解
当我们的项目足够大,使用的组件就会很多,此时如果一次性加载所有的组件是比较花费时间的.一开始就把所有的组件都加载是没必要的一笔开销,此时可以用异步组件来优化一下. 异步组件简单的说就是只有等到在页面里 ...
随机推荐
- C++ 类的static静态成员
静态static 静态成员的提出是为了解决数据共享的问题.实现共享有许多方法,如:设置全局性的变量或对象是一种方法.但是,全局变量或对象是有局限性的. 在全局变量前,加上关键字static该变量就被定 ...
- Disruptor系列(三)— 组件原理
前言 前两篇文章都是从github wiki中翻译而来,旨在能够快速了解和上手使用Disruptor.但是为了能够掌握该技术的核心思想,停留在使用层面还远远不够,需要了解其设计思想,实现原理,故这篇从 ...
- RabbitMQ的安装与使用(Centos7,linux版本)
1.主流的消息中间件简单介绍哦. 1).ActiveMQ是Apache出品,最流行的,能力强劲的开源消息总线,并且它一个完全支持jms(java message service)规范的消息中间件.其丰 ...
- 通过对象方法获取委托_C#反射获取委托_
前言:时间紧,先写关键代码,以后优化: 在此感谢其他博友分享的文章,参考文章:C#反射委托创建器 1-定义含有委托的类: public class TimeCycle { /// <summar ...
- 最近的项目系之2——core3.0整合Autofac
1.前言 core3.0与之前版本相比,有一些brokenchanges,那周边一些配套组件往往也难逃brokenchanges,Autofac也不例外.这里重点关注core整合Autofac,与之前 ...
- PHP语法入门以及变量
1PHP语法入门 1.1PHP是编译型语言 编译语言和解释语言的区别在于是否保存最终的可执行程序. 1.2PHP定界符 因为PHP是脚本语言,所以需要定界符 <?php e ...
- vuepress1.x入门使用
要点: 1.用npm操作会有各种问题,用yarn取代之; 2.yarn可以用npm全局安装,而npm是node环境自带,node环境去官网下载安装; 3.没有必要全局安装vuepress 操作: 1. ...
- 使用原生Ajax进行用户名重复的检验
title: 使用原生Ajax进行用户名重复的检验(一) date: 2019-01-21 17:35:15 tags: [JavaScript,Ajax] --- Ajax的复习 距离刚开始学aja ...
- Metasploit漏洞扫描
Metasploit漏洞扫描 漏洞扫描是自动在目标中寻找和发现安全弱点. 漏洞扫描器会在网络上和对方产生大量的流量,会暴露自己的行为过程,如此就不建议你使用漏扫了. 基本的漏洞扫描 我们首先使用net ...
- Android框架Volley使用:Json请求实现
首先我们在项目中导入这个框架: implementation 'com.mcxiaoke.volley:library:1.0.19' 在AndroidManifest文件当中添加网络权限: < ...