【面试题】 vue为什么v-for的优先级比v-if的高?
前言
有时候有些面试中经常会问到v-for与v-if谁的优先级高,这里就通过分析源码去解答一下这个问题。
下面的内容是在 当我们谈及v-model,我们在讨论什么?的基础上分析的,所以阅读下面内容之前可先看这篇文章。
继续从编译出发
以下面的例子出发分析:
newVue({
el:'#app',
template:`
<ul>
<li v-for="(item,index) in items" v-if="index!==0">
{{item}}
</li>
</ul>
`
})
从上篇文章可以知道,编译有三个步骤
parse : 解析模板字符串生成 AST语法树
optimize : 优化语法树,主要时标记静态节点,提高更新页面的性能
codegen : 生成js代码,主要是render函数和staticRenderFns函数
我们再次顺着这三个步骤对上述例子进行分析。
parse
parse过程中,会对模板使用大量的正则表达式去进行解析。开头的例子会被解析成以下AST节点:
// 其实ast有很多属性,我这里只展示涉及到分析的属性
ast = {
'type': 1,
'tag': 'ul',
'attrsList': [],
attrsMap: {},
'children': [{
'type': 1,
'tag': 'li',
'attrsList': [],
'attrsMap': {
'v-for': '(item,index) in data',
'v-if': 'index!==0'
},
// v-if解析出来的属性'if': 'index!==0',
'ifConditions': [{
'exp': 'index!==0',
'block': // 指向el自身
}],
// v-for解析出来的属性'for': 'items',
'alias': 'item',
'iterator1': 'index',
'parent': // 指向其父节点'children': [
'type': 2,
'expression': '_s(item)''text': '{{item}}',
'tokens': [
{'@binding':'item'},
]
]
}]
}
对于v-for指令,除了记录在attrsMap和attrsList,还会新增for(对应要遍历的对象或数组),alias,iterator1,iterator2对应v-for指令绑定内容中的第一,第二,第三个参数,开头的例子没有第三个参数,因此没有iterator2属性。
对于v-if指令,把v-if指令中绑定的内容取出放在if中,与此同时初始化ifConditions属性为数组,然后往里面存放对象:{exp,block},其中exp存放v-if指令中绑定的内容,block指向el。
optimize 过程在此不做分析,因为本例子没有静态节点。
codegen
上一篇文章从const code = generate(ast, options)开始分析过其生成代码的过程,generate内部会调用genElement用来解析el,也就是AST语法树。我们来看一下genElement的源码:
exportfunctiongenElement (el: ASTElement, state: CodegenState): string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}
if (el.staticRoot && !el.staticProcessed) {
returngenStatic(el, state)
} elseif (el.once && !el.onceProcessed) {
returngenOnce(el, state)
// 其实从此处可以初步知道为什么v-for优先级比v-if高,// 因为解析ast树生成渲染函数代码时,会先解析ast树中涉及到v-for的属性// 然后再解析ast树中涉及到v-if的属性// 而且genFor在会把el.forProcessed置为true,防止重复解析v-for相关属性
} elseif (el.for && !el.forProcessed) {
returngenFor(el, state)
} elseif (el.if && !el.ifProcessed) {
returngenIf(el, state)
} elseif (el.tag === 'template' && !el.slotTarget && !state.pre) {
returngenChildren(el, state) || 'void 0'
} elseif (el.tag === 'slot') {
returngenSlot(el, state)
} else {
// component or elementlet code
if (el.component) {
code = genComponent(el.component, el, state)
} else {
let data
if (!el.plain || (el.pre && state.maybeComponent(el))) {
data = genData(el, state)
}
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${el.tag}'${ data ? `,${data}` : '' // data }${ children ? `,${children}` : '' // children })`
}
// module transformsfor (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}
return code
}
}
接下来依次看看genFor和genIf的函数源码:
exportfunctiongenFor (el, state , altGen, altHelper) {
const exp = el.forconst alias = el.aliasconst iterator1 = el.iterator1 ? `,${el.iterator1}` : ''const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
el.forProcessed = true// avoid recursionreturn`${altHelper || '_l'}((${exp}),` +
`function(${alias}${iterator1}${iterator2}){` +
`return ${(altGen || genElement)(el, state)}` + //递归调用genElement'})'
}
在我们的例子里,当他处理li的ast树时,会先调用genElement,处理到for属性时,此时forProcessed为虚值,此时调用genFor处理li树中的v-for相关的属性。然后再调用genElement处理li树,此时因为forProcessed在genFor中已被标记为true。因此genFor不会被执行,继而执行genIf处理与v-if相关的属性。
exportfunctiongenIf (el,state,altGen,altEmpty) {
el.ifProcessed = true// avoid recursion// 调用genIfConditions主要处理el.ifConditions属性returngenIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}
functiongenIfConditions (conditions, state, altGen, altEmpty) {
if (!conditions.length) {
return altEmpty || '_e()'// _e用于生成空VNode
}
const condition = conditions.shift()
if (condition.exp) { //condition.exp即v-if绑定值,例子中则为'index!==0'// 生成一段带三目运算符的js代码字符串return`(${condition.exp})?${ genTernaryExp(condition.block) }:${ genIfConditions(conditions, state, altGen, altEmpty) }`
} else {
return`${genTernaryExp(condition.block)}`
}
// v-if with v-once should generate code like (a)?_m(0):_m(1)functiongenTernaryExp (el) {
return altGen
? altGen(el, state)
: el.once
? genOnce(el, state)
: genElement(el, state)
}
}
最后,经过codegen生成的js代码如下:
functionrender() {
with(this) {
return_c('ul', _l((items), function (item, index) {
return (index !== 0) ? _c('li') : _e()
}), 0)
}
}
其中:
_c: 调用 createElement 去创建 VNode
_l: renderList函数,主要用来渲染列表
_e: createEmptyVNode函数,主要用来创建空VNode
总结
为什么v-for的优先级比v-if的高?总结来说是编译有三个过程,parse->optimize->codegen。在codegen过程中,会先解析AST树中的与v-for相关的属性,再解析与v-if相关的属性。除此之外,也可以知道Vue对v-for和v-if是怎么处理的。
大厂面试题分享 面试题库
前后端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了