zoukankan      html  css  js  c++  java
  • 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。

  • 相关阅读:
    典型页面布局
    网站表单输入框去除浏览器默认样式
    时间格式问题
    经典算法
    css自动换行
    git pull报“unable to update local ref”解决方案
    MYSQL数据插入和更新的语法
    正则表达式去除连续重复的字符
    linux保存住github的账号和密码
    php动态获取常量
  • 原文地址:https://www.cnblogs.com/greatdesert/p/11157771.html
Copyright © 2011-2022 走看看