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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM