在开发中,咱们可能会写出以下代码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)建立一个空节点,其实是一个没有文本的注释节点markdown
// createEmptyVNode建立一个默认为空文本的注释节点 export const createEmptyVNode = (text: string = '') => { const node = new VNode() node.text = text node.isComment = true return node } 复制代码
咱们在控制台中能够看到这个空的注释节点,经过对比list数据,能够知道,这个注释节点就是那个age>30
的item
app
经过这个小实验咱们已经可以知道v-for确实比v-if的优先级更高了,可是你可能想问了,为何你是这样的render函数?😂dom
那么咱们再进一步的去探索生成render函数的函数
咱们最终在compiler
模版编译器找到了答案ide
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 } } 复制代码
讲到这里,官方说的第一个问题就分析完了,那么说的第二点又是什么意思呢?函数
这里为何说每次从新渲染的时候都要遍历整个列表?实际上是这样的,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进行渲染,尽量的提升性能。