当前位置: 萬仟网 > IT编程>网页制作>Html5 > Vue.js 源码分析(二十三) 指令篇 v-show指令详解

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

2019年07月11日 14:29  | 萬仟网IT编程  | 我要评论

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。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

◎已有 0 人评论

Copyright © 2019  萬仟网 保留所有权利. 粤ICP备17035492号-1
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com