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