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树上。