【面试题】 vue为什么v-for的优先级比v-if的高?
创始人
2025-05-28 02:00:06
0

前言


有时候有些面试中经常会问到v-for与v-if谁的优先级高,这里就通过分析源码去解答一下这个问题。

下面的内容是在 当我们谈及v-model,我们在讨论什么?的基础上分析的,所以阅读下面内容之前可先看这篇文章。

继续从编译出发


以下面的例子出发分析:

newVue({el:'#app',template:`
  • {{item}}
` })

从上篇文章可以知道,编译有三个步骤

  • 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 codeif (el.component) {code = genComponent(el.component, el, state)} else {let dataif (!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)}
}

其中:

  1. _c: 调用 createElement 去创建 VNode

  1. _l: renderList函数,主要用来渲染列表

  1. _e: createEmptyVNode函数,主要用来创建空VNode

总结


为什么v-for的优先级比v-if的高?总结来说是编译有三个过程,parse->optimize->codegen。在codegen过程中,会先解析AST树中的与v-for相关的属性,再解析与v-if相关的属性。除此之外,也可以知道Vue对v-for和v-if是怎么处理的。

大厂面试题分享 面试题库

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库

相关内容

热门资讯

腌制美味脆爽酸辣白萝卜,解锁冬... 在寒风凛冽的冬日里,一道酸辣脆爽的白萝卜小菜,无疑能为餐桌增添一抹亮色,温暖人心。今天,作为你们的美...
福建人私藏的 10 家小吃店,... 福建美食藏龙卧虎,本地人私藏的 10 家小吃店更是美味的宝藏。从让外地人改观的土笋冻,到令人垂涎的蚵...
老公从挑食到光盘的8道家常菜,... #图文打卡计划# 结婚五年才发现,老公挑食的毛病不是治不好——是没遇上能镇住味蕾的"硬菜"!从干椒葱...
冬日里的温暖滋味:白菜香菇胡萝... 在寒风凛冽的冬日里,每个人都渴望一份温暖而美味的慰藉。忙碌的生活节奏和不断累积的压力,让人们的身体容...
“老干妈”的传奇之路:从路边摊... 在美食的世界里,有一种味道,它不仅仅是一种调味品,更是一种情感的寄托,一种文化的传承。这种味道,就是...
暴吵萌厨新手攻略,厨神必学技巧 还在被顾客催单催到手忙脚乱?别慌!专为厨房小白设计,用最接地气的操作和最野的套路,鸟人云教你化身厨房...
原创 四... 四川,这座被誉为"天府之国"的旅游文化名城,更是享誉全球的"美食天堂"。作为中国传统"四大菜系"之一...
山东日照:茶香漫青山 绿意生金... 5月23日,位于日照市岚山区碑廓镇的浏园春有机茶园。 山东省日照市岚山区在“南茶北引”试种成功的基础...
德州扒鸡的 “摇滚新生”甜辣鸡... 本文聚焦德州扒鸡推出的 “摇滚新生” 甜辣鸡爪与音乐节跨界合作这一创新现象。通过剖析其将非遗美食与现...
端午将至,没有一个人能空手从贵... 端午节的脚步渐近 贵阳市文笔街的空气中 已弥漫着粽叶与糯米的清香 这条不足百米的街巷 因聚集十余家 ...
NTT计算实例by ChatG... 假设我们要计算多项式 f(x) = x^3 + 2x^2 + x +...
河南景区招帅哥NPC月薪开到3... 01.盒马卖含褪黑素的晚安牛奶 02.乐高推出福尔摩斯贝克街图书角 03.星巴克招聘飞行员年薪最高3...
听说你端午节想出去玩?注意!这... 端午假期马上就要到了 很多人做好了出游计划 乐逛景区,安全第一 近日,文化和旅游部组织多个暗访小组对...
java Vector 源码分... Vector类的底层实现Vector类 VS ArrayList类Vector类源码解读无参构造——...
露营经济崛起:如何精明选购露营... 近年来,露营经济持续升温,从城市周边到深山野林,露营已成为都市人亲近自然、放松身心的热门选择,而设备...
五一假期户外游玩这份防虫指南能... 怎么说呢,每年一到小长假,朋友圈就被各种美景照片刷屏。但你可能不知道,那些看似岁月静好的九宫格背后,...
基于微信小程序的图书馆座位预约... 1. 系统开发背景 图书馆因有良好的学习氛围、大量的学习资源吸引大家前来学习,图书馆还...