写点什么

vue 为什么 v-for 的优先级比 v-if 的高?

作者:bb_xiaxia1998
  • 2023-01-02
    浙江
  • 本文字数:2894 字

    阅读完需:约 9 分钟

前言

有时候有些面试中经常会问到v-forv-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 语法树

  • 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指令,除了记录在attrsMapattrsList,还会新增for(对应要遍历的对象或数组),aliasiterator1,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的源码:


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 }}
复制代码


接下来依次看看genForgenIf的函数源码:


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 '})'}
复制代码


在我们的例子里,当他处理liast树时,会先调用genElement,处理到for属性时,此时forProcessed为虚值,此时调用genFor处理li树中的v-for相关的属性。然后再调用genElement处理li树,此时因为forProcessedgenFor中已被标记为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)  }}
复制代码


其中:


  1. _c: 调用 createElement 去创建 VNode

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

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

总结

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


用户头像

bb_xiaxia1998

关注

还未添加个人签名 2022-09-01 加入

还未添加个人简介

评论

发布
暂无评论
vue为什么v-for的优先级比v-if的高?_Vue_bb_xiaxia1998_InfoQ写作社区