Vue.js 源碼分析(二十三) 指令篇 v-show指令詳解

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。

相關文章
相關標籤/搜索