Code前端首页关于Code前端联系我们

【Vue源码】为什么v-for的优先级比v-if高?

terry 2年前 (2023-09-08) 阅读数 159 #Vue

前言

有时在一些采访中,经常会被问到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语法树
  • 优化:优化语法树,在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对于指令forfor(对应要遍历的对象或字符串),除了中的条目外,还将添加aliasiterator1
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
  }
}
 

我们看一下函数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
    '})'
}
 

在我们的示例中,处理树时,它将首先调用genElement树中的v-for属性li。然后调用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. _ecreateEmptyVNode功能,多用于创建空的VNode

总结

为什么v-for比v-if优先级高?综上所述,共有三个编译过程,parse->optimize->codegen。在过程

中,将首先解析树AST中与v-for关联的属性,然后解析与v-if关联的属性。此外,您还可以了解Vue如何对待v-forv-if

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门