在開發中,咱們可能會寫出以下代碼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進行渲染,儘量的提升性能。