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模块的bint函数 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,该函数如下:
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。
上一篇: MS SQL读取JSON数据
下一篇: vue总线机制(bus)知识点详解
推荐阅读
-
Vue.js 源码分析(二十二) 指令篇 v-model指令详解
-
Vue.js 源码分析(十六) 指令篇 v-on指令详解
-
Vue.js 源码分析(十四) 基础篇 组件 自定义事件详解
-
Vue.js 源码分析(十二) 基础篇 组件详解
-
Vue.js 源码分析(二十四) 高级应用 自定义指令详解
-
Vue.js 源码分析(十五) 指令篇 v-bind指令详解
-
Vue.js 源码分析(十一) 基础篇 过滤器 filters属性详解
-
Vue.js 源码分析(十八) 指令篇 v-for 指令详解
-
Vue.js 源码分析(十三) 基础篇 组件 props属性详解
-
Vue.js 源码分析(十七) 指令篇 v-if、v-else-if和v-else 指令详解