欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-20 17:39:51
v-show的作用是将表达式值转换为布尔值,根据该布尔值的真假来显示/隐藏切换元素,它是通过切换元素的display这个css属性值来实现的,例如: 渲染结果为: 当我们在修改isShow为false时: 页面里的Hello Vue!就隐藏部件了,我们查看DOM结构如下: 可以看到Vue是通过修改d ......

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>

渲染结果为:

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

当我们在修改isshow为false时:

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

页面里的hello vue!就隐藏部件了,我们查看dom结构如下:

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

可以看到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对象如下:

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

接下来在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。