v-show的做用是將表達式值轉換爲布爾值,根據該布爾值的真假來顯示/隱藏切換元素,它是經過切換元素的display這個css屬性值來實現的,例如: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>
渲染結果爲:html
當咱們在修改isShow爲false時:vue
頁面裏的Hello Vue!就隱藏部件了,咱們查看DOM結構以下:node
能夠看到Vue是經過修改display這個CSS屬性來隱藏元素的express
源碼分析npm
在解析模板將DOM轉換成AST對象的時候會執行processAttrs()函數,以下:app
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函數,以下:this
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,該函數以下:
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。