在开发中,咱们可能会写出以下代码javascript
<!-- html模版 -->
<div id="app">
<ul>
<li v-for="item in list" v-if="item.age<30">
<span>{{item.name}}</span>
<span>{{item.age}}</span>
</li>
</ul>
</div>
复制代码
// 列表数据
list: [
{
name: 'jack',
age: 23
},
{
name: 'john',
age: 33
},
{
name: 'petty',
age: 20
},
]
复制代码
这个操做看起来很简单,就是过滤要展现的列表,可是官方是不推荐这么写的,官方连接。 官方给出了两点缘由:html
懒得看原文的能够看下面的截图:vue
经过上文的描述,大概是懂了,嗯。。。可是看完仍是不知因此然。
好比官网说,v-for比v-if优先级更高,为何呢?你说优先就优先?🤔
咱们能够作个简单的小实验,就是打印一下render函数,看一下vue对这两个指令是如何解析的。java
// 打印出来的render函数
(function anonymous() {
with (this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('ul', _l((list), function(item) {
return (item.age < 30) ? _c('li', [_c('span', [_v(_s(item.name))]), _v(" "), _c('span', [_v(_s(item.age))])]) : _e()
}), 0)])
}
})
复制代码
直接看这个代码可能不知道各个函数名是什么意思,咱们打开源码,能够看到在renderMixin
的时候会把vue的原型传入下面的方法node
// renderMixin方法执行时注册渲染快捷方法,所有挂载在vue原型上
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
export function installRenderHelpers (target: any) {
target._o = markOnce
target._n = toNumber
target._s = toString
target._l = renderList
target._t = renderSlot
target._q = looseEqual
target._i = looseIndexOf
target._m = renderStatic
target._f = resolveFilter
target._k = checkKeyCodes
target._b = bindObjectProps
target._v = createTextVNode
target._e = createEmptyVNode
target._u = resolveScopedSlots
target._g = bindObjectListeners
target._d = bindDynamicKeys
target._p = prependModifier
}
// _c方法在render.js中定义,表示createElement
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
复制代码
经过上述的函数映射关系,咱们能够知道,vue经过_l
(renderList)函数遍历list,在函数内部再经过三目语句处理v-if指令,若是条件为true,则建立li及子节点,不然执行_e
(createEmptyVNode)建立一个空节点,其实是一个没有文本的注释节点app
// createEmptyVNode建立一个默认为空文本的注释节点
export const createEmptyVNode = (text: string = '') => {
const node = new VNode()
node.text = text
node.isComment = true
return node
}
复制代码
咱们在控制台中能够看到这个空的注释节点,经过对比list数据,能够知道,这个注释节点就是那个age>30
的item
dom
经过这个小实验咱们已经可以知道v-for确实比v-if的优先级更高了,可是你可能想问了,为何你是这样的render函数?😂ide
那么咱们再进一步的去探索生成render函数的函数
咱们最终在compiler
模版编译器找到了答案函数
export function generate ( ast: ASTElement | void, options: CompilerOptions ): CodegenResult {
const state = new CodegenState(options)
const code = ast ? genElement(ast, state) : '_c("div")'
return {
// 咱们的render函数就是在这里生成的,里面的code经过下面的genElement方法生成
render: `with(this){return ${code}}`,
staticRenderFns: state.staticRenderFns
}
}
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
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
// 而后再处理v-if
} 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
}
}
复制代码
讲到这里,官方说的第一个问题就分析完了,那么说的第二点又是什么意思呢?oop
这里为何说每次从新渲染的时候都要遍历整个列表?实际上是这样的,render函数执行之后会生成vnode,就是虚拟dom。每当数据发生变化时,会触发watcher执行update方法,就会从新执行render方法生成新的vnode,因此就须要从新遍历一遍数据
// 每一个组件初始化挂载时(mountComponent)会定义一个渲染watcher
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */
)
// 每当组件数据变化时,就会执行这个方法
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
复制代码
还有最后一句话不知道你注意到了没有 不论活跃用户是否发生了变化。你可能会问了,难道个人list数据没有发生变化也要从新遍历?
是的,在vue2中,为了优化性能,将watcher的粒度放大,变为一个组件一个watcher(用户自定义的watcher除外),这样,数据变化就只能通知到组件这一级别,至于组件里面到底哪一个数据发生了变化,应该更新哪一个节点,须要依靠新老数据生成的vnode虚拟节点进行diff对比才能知道。
讲到这里,你们应该已经清楚了那篇文档的良苦用心了吧😂。咱们在实际的开发中,应该尽可能避免这种写法。若是要过滤数据,可使用计算属性进行过滤,而后再丢给vue进行渲染,尽量的提升性能。