乐趣区

关于vue.js:vue为什么vfor的优先级比vif的高

前言

有时候有些面试中常常会问到 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 是怎么解决的。

退出移动版