前言
有时在一些采访中,经常会被问到v-for
和 v-if
之间谁的优先级更高。下面我通过分析源码来回答这个问题。
以下内容是根据我们谈论v-model时所讨论的内容进行分析的,所以在阅读以下内容之前,您可以先阅读这篇文章。
继续翻译
让我们从以下示例开始:
new Vue({
el:'#app',
template:`
<ul>
<li v-for="(item,index) in items" v-if="index!==0">
{{item}}
</li>
</ul>
`
})
从上一篇文章我们可以知道,编译分为三步
- parse:解析模板字符串以创建AST语法树
- 优化:优化语法树,在main中高亮静态节点,提高页面更新性能
- codegen:生成js代码,主要是render函数和staticRenderFns函数
让我们通过这三个步骤重新分析一下上面的例子。
崩溃
在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'},
]
]
}]
}
attrsList
对于指令for
、for
(对应要遍历的对象或字符串),除了中的条目外,还将添加alias
、iterator1
、new Vue({
el:'#app',
template:`
<ul>
<li v-for="(item,index) in items" v-if="index!==0">
{{item}}
</li>
</ul>
`
})
和attrsList
指令 绑定内容中的第一、第二、第三个参数。该示例最初没有第三个参数,因此没有属性 iterator2
。
指令ifConditions
后,将指令ifConditions
绑定的内容取出,放入if
中,同时将属性ifConditions
初始化为数组,然后在其中存储对象:{exp,block}
其中 exp
存储内容 ,绑定到命令 ,block
指向 el
。
optimize,因为本例中没有静态节点。
代码生成
genElement
上一篇文章从const code = generate(ast, options)
分析了代码生成过程。 generate
会内部调用genElement
来解析el
,即语法树AST
。我们来看看genElement
的源码:
export function genElement (el: ASTElement, state: CodegenState): string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
// 其实从此处可以初步知道为什么v-for优先级比v-if高,
// 因为解析ast树生成渲染函数代码时,会先解析ast树中涉及到v-for的属性
// 然后再解析ast树中涉及到v-if的属性
// 而且genFor在会把el.forProcessed置为true,防止重复解析v-for相关属性
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
} else {
// component or element
let 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 transforms
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}
return code
}
}
我们看一下函数genFor
和genIf
的源码:
export function genFor (el, state , altGen, altHelper) {
const exp = el.for
const alias = el.alias
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
el.forProcessed = true // avoid recursion
return `${altHelper || '_l'}((${exp}),` +
`function(${alias}${iterator1}${iterator2}){` +
`return ${(altGen || genElement)(el, state)}` + //递归调用genElement
'})'
}
在我们的示例中,处理树时,它将首先调用genElement
树中的v-for
属性li
。然后调用genElement
来处理树li
,因为forProcessed
在genFor
中被标记为true
。因此,genFor
不会被执行,然后执行genIf
来处理与v-if
关联的属性。
export function genIf (el,state,altGen,altEmpty) {
el.ifProcessed = true // avoid recursion
// 调用genIfConditions主要处理el.ifConditions属性
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}
function genIfConditions (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)
function genTernaryExp (el) {
return altGen
? altGen(el, state)
: el.once
? genOnce(el, state)
: genElement(el, state)
}
}
最后codegen生成的js代码如下:
function render() {
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
。在过程
中,将首先解析树AST
中与v-for
关联的属性,然后解析与v-if
关联的属性。此外,您还可以了解Vue
如何对待v-for
和v-if
。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。