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 源码分析(二十七) 高级应用 异步组件 详解
当我们的项目足够大,使用的组件就会很多,此时如果一次性加载所有的组件是比较花费时间的.一开始就把所有的组件都加载是没必要的一笔开销,此时可以用异步组件来优化一下. 异步组件简单的说就是只有等到在页面里 ...
随机推荐
- jxl解析excel时,处理中文乱码问题
jxl解析excel时,处理中文乱码问题 一般出现较多的问题是,当exce中包含了中文或特殊字符时,在解析时候就会出现乱码现象. 解决方法为: InputStream in = new FileInp ...
- HashMap框架源码深入解读,面试不用愁
在Java Collections Framework的体系中中,主要有两个重要的接口,一个是List.Set和Queue所属的Collection,还有一个就是Map接口了.在上一篇文章中介绍了Li ...
- 1.springMVC Controller配置方式
一.手动配置方式 1.web.xml中DispatcherServlet控制器的的配置 SpringMVC也是一种基于请求驱动的WEB框架,并且使用了前端控制器的设计模式.前端控制器就是Dispatc ...
- MIME格式解析
- 邮件例子 一个MIME格式的邮件例子如下: Return-Path: <mlemos@acm.org> To: Manuel Lemos <mlemos@linux.local& ...
- [转]Outlook API
本文转自:https://www.cnblogs.com/yl153/p/6711519.html 1.Outlook简介 若要从Outlook 外控制Outlook对象,必须在编写代码的工程中建立对 ...
- 【转载】如何在Android中避免创建不必要的对象
在编程开发中,内存的占用是我们经常要面对的现实,通常的内存调优的方向就是尽量减少内存的占用.这其中避免创建不必要的对象是一项重要的方面. Android设备不像PC那样有着足够大的内存,而且单个App ...
- Activit 5.13 工作流部署新版本后回退到上一个版本
有时因为某些原因Activit流程部署新版本后,还没有发起流程,回退到上一个版本.操作过程: 1.查询版本更新记录,记录字段ID_值,假设值为100: select to_char(t.deploy_ ...
- MySQL 设置表注释
新增表增加注释 CREATE TABLE sys_tables ( owner ) NOT NULL COMMENT '归属用户', table_name ) NOT NULL COMMENT '表名 ...
- toast文本提示信息元素获取
在做自动化的过程中,我们有可能会遇到toast提示语,这种提示语只会显示一两秒的样子,导致我们获取元素很困难 今天总结下怎么获取这个toast元素吧 例如下图所示的这种toast提示 图中红框的这种元 ...
- Node.js—概述
一.Node.js与其他语言对比 Node.js不是一种独立的语言,与PHP.JSP.Python.Perl.Ruby的"既是语言,也是平台"不同,Node.js的使用Java ...