每年的金三銀四,都會有很多人都躍躍欲試了,但是再怎麼着急也要先保證自己的技術達标,這樣才能從容應對各種考驗,這裡我們就開始複盤Vue中常見的面試問題,看看自己都掌握了嘛~
面試官:請你說一下Vue中v-if和v-for的優先級誰更高吧?如果兩個在同一個标簽上出現,會出現什麼問題嘛?怎麼解決這個問題呢?
我:......(好像會,但是具體不太知道咋說)。
老實說,咋一聽到這個題,如果自己沒有試驗過感覺還真容易說錯,就像是會進入一個“思維誤區”,就會理所當然的想如果這兩個指令同時出現一個标簽那是不是應該先if判斷是否存在才會去渲染for的循環結構?但實際是這樣嗎?接下來我們就通過案例和源碼檔案的檢視來徹底解決這個常見的面試題。
一、v-if&v-for簡介
v-if
v-if 指令用于條件性地渲染一塊内容。這塊内容隻會在指令的表達式傳回 true值的時候被渲染。
v-for
v-for 指令基于一個數組來渲染一個清單。v-for 指令需要使用 item in items 形式的特殊文法,其中 items 是源資料數組或者對象,而 item 則是被疊代的數組元素的别名。
v-for使用 的時候,建議設定key值,并且保證每個key值是獨一無二的,這便于diff算法進行優化。
v-if&v-for使用
<ul>
<li v-for='user in users' :key='user.id'>{{user.name}}</li>
</ul>
<footer v-if='user'>{{user}}</footer>
二、v-if&v-for優先級比較
v-if與v-for都是vue模闆系統中的指令。
在vue模闆編譯的時候,會将指令系統轉化成可執行的render函數。
v-if與v-for在同一标簽
編寫一個p标簽,同時使用v-if與 v-for:
<div id="app">
<p v-for="user in users" v-if='user.age>18'>
{{user.name}}
</p>
</div>
模闆指令的代碼都會生成在render函數中,通過app.$options.render就能得到渲染函數。
console.log('輸出render函數', app.$options.render);
控制台輸出結果為:
(function anonymous(
) {
with(this){
return _c('div',{attrs:{"id":"app"}},
_l((users),
function(user){return (user.age>18)?_c('p',[_v("\n"+_s(user.name)+"\n")]):_e()}),0)}
})
_l是vue的清單渲染函數,作用就是循環處理users的,第6行就是嵌套在_l函數中的,那這個結果就是每循環一次就去執行裡面的函數一次,我們看到最後的結果就是return後面的,也就是說,-l函數包含了user.age>18的判斷,也就是:每循環一次清單,就會判斷一次user.age>18。
初步得到結論:v-for優先級是比v-if高。
v-if與v-for在不同标簽
先上代碼:
<div id="app">
<template v-if="isShow">
<p v-for="user in users">{{user.name}}</p>
</template>
</div>
再輸出render函數:
ƒ anonymous(
) {
with(this){
return _c('div',{attrs:{"id":"app"}},
[(isShow)?_l((users),
function(user){return _c('p',[_v(_s(user.name))])}):_e()],2)}
這時候我們可以看到v-for與v-if作用在不同标簽時候,是先進行判斷,再進行清單的渲染。
三、Vue源碼解析
通過前面的案例,我們可以知道v-for的優先級比v-if的優先級高,但是要知道具體原因,需要去vue源碼中找:
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
...
}
通過第9行和第11行,我們可以知道源碼是先判斷for再判斷if,就證明v-for比v-if的優先級更高。
四、遇到的問題和解決方案
問題:永遠不要把v-if和v-for同時用在同一個元素上,會帶來性能方面的浪費(每次渲染都會先循環再進行條件判斷)。
為了避免出現這種情況,有如下兩種解決辦法:
使用template
在外層嵌套template(頁面渲染不生成dom節點),在這一層進行v-if判斷,然後在内部進行v-for循環。
<template v-if="isShow">
<p v-for="user in users">{{user.name}}</p>
</template>
借助computed
如果條件出現在循環内部,可通過計算屬性computed提前過濾掉那些不需要顯示的項。
<p v-for="user in usersList">{{user.name}}</p>
<script>
new Vue({
computed:{
usersList(){
return this.users.filter(user=>user.age>18)
}
}
})
</script>
五、總結
回歸剛開始的面試題,可以得出結論:
- v-for比v-if優先級更高(可以說清結論的來源更好)。
- 如果同時出現在同一個标簽上,則每次渲染都會先執行循環再進行條件判斷,會造成較大的性能浪費。
解決辦法有兩種:
先在外層套一個template來放置v-if,再嵌套v-for。
借助computed計算屬性代替v-if。
注:Vue 3.0 已經調整了v-if和v-for 的優先級,故不存在渲染性能問題。