v-if和v-show的区别,很多人可能知其然,不知其所以然。

353 阅读2分钟

v-if和v-show的区别可能是Vue面试中最常被问到的问题。下面我们就一起来探讨他们的根本区别。

v-if

在Vue源码中定义了一个type叫做ASTElement

    // flow/compiler.js
    declare type ASTElement = {
        ...
        
        if?: string;
        ifProcessed?: boolean;
        elseif?: string;
        else?: true;
        ifConditions?: ASTIfConditions;
    }

在由render函数生成的AST语法树结构中,每一个element都可能会有这些属性。其中ifConditions里面存放着所有的expression。 这些表达式是由在解析模板节点上v-if/v-else-if/v-else而来。

   // complier/parse/index.js
   
   /**
    * Convert HTML string to AST.
    */
   export function parse(
     template: string,
     options: CompilerOptions
   ): ASTElement | void {
       ...
       
       processIf(element)
       
       ...
   }
   
   function processIf(el) {
     const exp = getAndRemoveAttr(el, 'v-if')
     if (exp) {
       el.if = exp
       addIfCondition(el, {
         exp: exp,
         block: el
       })
     } else {
       if (getAndRemoveAttr(el, 'v-else') != null) {
         el.else = true
       }
       const elseif = getAndRemoveAttr(el, 'v-else-if')
       if (elseif) {
         el.elseif = elseif
       }
     }
   }
   
   export function addIfCondition(el: ASTElement, condition: ASTIfCondition) {
     if (!el.ifConditions) {
       el.ifConditions = []
     }
     el.ifConditions.push(condition)
   }

从上面的源码中我们可以看到,当我们把template转变成AST语法树的时候,会用processIf方法处理v-if/v-else-if/v-else。 如果节点上有这些指令,就把它push到el的ifConditions中去。

   // complier/codegen/index.js
   
   export function generate (
     ast: ASTElement | void,
     options: CompilerOptions
   ): CodegenResult {
     const state = new CodegenState(options)
     const code = ast ? genElement(ast, state) : '_c("div")'
     return {
       render: `with(this){return ${code}}`,
       staticRenderFns: state.staticRenderFns
     }
   }
   
   export function genElement (el: ASTElement, state: CodegenState): string {
       ...
       
       } else if (el.if && !el.ifProcessed) {
           return genIf(el, state)
           
           ...
   }
   
   function genIfConditions (
     conditions: ASTIfConditions,
     state: CodegenState,
     altGen?: Function,
     altEmpty?: string
   ): string {
     ...
       
     const condition = conditions.shift()
     if (condition.exp) {
       return `(${condition.exp})?${
         genTernaryExp(condition.block)
       }:${
         genIfConditions(conditions, state, altGen, altEmpty)
       }`
     } else {
       return `${genTernaryExp(condition.block)}`
     }
   
     ...
   }
   
   // complier/index.js
   
   export const createCompiler = createCompilerCreator(function baseCompile (
     template: string,
     options: CompilerOptions
   ): CompiledResult {
     const ast = parse(template.trim(), options)
     if (options.optimize !== false) {
       optimize(ast, options)
     }
     const code = generate(ast, options)
     return {
       ast,
       render: code.render,
       staticRenderFns: code.staticRenderFns
     }
   })

上面的createComplier方法就是vm._render方法的主体部分。

先利用parse方法对html模板进行解析,然后再利用generate方法生成renderred后的数据。 在generate方法中有一个genElement方法,genElement方法中有对ifCondition的判断。 如果confition的exp为false的话是不会在后面的Vnode中显示出来的。

v-if在整个渲染流程的使用就到此为止。

v-show

// platforms/web/runtime/directives/show.js

export default {
      bind (el: any, { value }: VNodeDirective, vnode: VNodeWithData) {
        vnode = locateNode(vnode)
        const transition = vnode.data && vnode.data.transition
        const originalDisplay = el.__vOriginalDisplay =
          el.style.display === 'none' ? '' : el.style.display
        if (value && transition) {
          vnode.data.show = true
          enter(vnode, () => {
            el.style.display = originalDisplay
          })
        } else {
          el.style.display = value ? originalDisplay : 'none'
        }
      },
    
      update (el: any, { value, oldValue }: VNodeDirective, vnode: VNodeWithData) {
        /* istanbul ignore if */
        if (!value === !oldValue) return
        vnode = locateNode(vnode)
        const transition = vnode.data && vnode.data.transition
        if (transition) {
          vnode.data.show = true
          if (value) {
            enter(vnode, () => {
              el.style.display = el.__vOriginalDisplay
            })
          } else {
            leave(vnode, () => {
              el.style.display = 'none'
            })
          }
        } else {
          el.style.display = value ? el.__vOriginalDisplay : 'none'
        }
      },
    
      unbind (
        el: any,
        binding: VNodeDirective,
        vnode: VNodeWithData,
        oldVnode: VNodeWithData,
        isDestroy: boolean
      ) {
        if (!isDestroy) {
          el.style.display = el.__vOriginalDisplay
        }
      }
}

这个v-show指令对应的指令对象 有bind/unbind/update方法用于绑定、解绑和更新元素的一个style ==> display

也就是说v-show是通过控制元素的display样式来控制元素的隐藏和显示的。

总结

v-if不会被运用到VNode或者真实Dom,VNode是由_render函数生成的, 而v-if指令在_render函数中已经被处理完了。不论是初始化还是数据更新,它都会在_render的时候被处理。

而v-show是会被作用在真实Dom节点上的,通过display的值来控制Dom的显示或者隐藏。

小提示: display为none的元素只会存在于dom树,不会被生成到render树上。