前端面试系列【005】 - v-for 和 v-if 谁的优先级更高

247 阅读2分钟

对于这个问题,针对目前应用范围更广的 vue 2.6.x 来做回答。

要弄清楚优先级问题,自然要从源码中来找答案,而这个答案在:compiler/codegen/index.js: 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)
  } 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
  }
}

从上面的代码可以看到,这里是先处理 for 然后处理 if,那么,显然 for 的优先级是高于 if 的。

扩展

对于这个问题,可以稍微扩展一下,引申出下面两个问题:

  • 如果 v-ifv-for 同时出现在一个标签中,会不会出现什么问题?
  • 如果出现问题,应该去怎么优化?
  • 在优化的时候,怎么让性能更好?

对于这些问题,通过一个 demo 就可以很好的说明:

<div id="demo">
  <h1>v-if 和 v-for</h1>
  <p v-for="a in arr" v-if="a.isShow" :key="a.txt">
    {{ a.txt }}
  </p>
</div>
<script>
  const app = new Vue({
    el: "#demo",
    data: {
      isShow: false,
      arr: [
        {
          txt: 1,
          isShow: false
        },
        {
          txt: 2,
          isShow: true
        },
        {
          txt: 3,
          isShow: false
        }
      ]
    }
  });
</script>

这里,预期是页面上只输出 2,来看看结果:

可以看到,1 和 3 都是先生成,然后被关闭的。

从结果上看,似乎没什么太大的问题,输出和预期是相符的,但实际上这样的操作实际上是非常消耗性能的。

为什么这么说呢,我们将 render 函数打印出来看看:

;(function anonymous() {
  with (this) {
    return _c(
      'div',
      { attrs: { id: 'demo' } },
      [
        _c('h1', [_v('v-if 和 v-for')]),
        _v(' '),
        _l(arr, function (a) {
          return a.isShow ? _c('p', { key: a.txt }, [_v('\n        ' + _s(a.txt) + '\n      ')]) : _e()
        }),
      ],
      2
    )
  }
})

可以看到,这里先渲染列表,然后在循环中通过判断 isShow 来是否渲染。什么意思呢,我们来看看上面代码的 dom 结构:

可以看到,1 和 3 实际上是被渲染出来,然后再被关掉的。(这不是多此一举嘛(=。=))

那么应该怎么优化呢?

这里可以利用 vue 的计算属性将 arr 过滤一遍,将需要渲染的值过滤出来即可:

computed: {
  showArr() {
    return this.arr.filter(a => a.isShow);
  }
}

结果如下:

dom 结构如下:

显然,这样处理之后性能肯定是高于上面的方案了。

结语

更佳阅读体验:005 - v-for 和 v-if 谁的优先级更高