vue為什么v-for的優(yōu)先級比v-if高原理解析
前言
有時候有些面試中經(jīng)常會問到v-for
與v-if
誰的優(yōu)先級高,這里就通過分析源碼去解答一下這個問題。
下面的內(nèi)容是在 當我們談及v-model,我們在討論什么?的基礎(chǔ)上分析的,所以閱讀下面內(nèi)容之前可先看這篇文章。
繼續(xù)從編譯出發(fā)
以下面的例子出發(fā)分析:
new Vue({ el:'#app', template:` <ul> <li v-for="(item,index) in items" v-if="index!==0"> {{item}} </li> </ul> ` })
從上篇文章可以知道,編譯有三個步驟
- parse : 解析模板字符串生成 AST語法樹
- optimize : 優(yōu)化語法樹,主要時標記靜態(tài)節(jié)點,提高更新頁面的性能
- codegen : 生成js代碼,主要是render函數(shù)和staticRenderFns函數(shù)
我們再次順著這三個步驟對上述例子進行分析。
parse
parse過程中,會對模板使用大量的正則表達式去進行解析。開頭的例子會被解析成以下AST
節(jié)點:
// 其實ast有很多屬性,我這里只展示涉及到分析的屬性 ast = { 'type': 1, 'tag': 'ul', 'attrsList': [], attrsMap: {}, 'children': [{ 'type': 1, 'tag': 'li', 'attrsList': [], 'attrsMap': { 'v-for': '(item,index) in data', 'v-if': 'index!==0' }, // v-if解析出來的屬性 'if': 'index!==0', 'ifConditions': [{ 'exp': 'index!==0', 'block': // 指向el自身 }], // v-for解析出來的屬性 'for': 'items', 'alias': 'item', 'iterator1': 'index', 'parent': // 指向其父節(jié)點 'children': [ 'type': 2, 'expression': '_s(item)' 'text': '{{item}}', 'tokens': [ {'@binding':'item'}, ] ] }] }
對于v-for
指令,除了記錄在attrsMap
和attrsList
,還會新增for
(對應(yīng)要遍歷的對象或數(shù)組),alias
,iterator1
,iterator2
對應(yīng)v-for
指令綁定內(nèi)容中的第一,第二,第三個參數(shù),開頭的例子沒有第三個參數(shù),因此沒有iterator2
屬性。
對于v-if
指令,把v-if
指令中綁定的內(nèi)容取出放在if
中,與此同時初始化ifConditions
屬性為數(shù)組,然后往里面存放對象:{exp,block}
,其中exp
存放v-if
指令中綁定的內(nèi)容,block
指向el
。
optimize 過程在此不做分析,因為本例子沒有靜態(tài)節(jié)點。
codegen
上一篇文章從const code = generate(ast, options)
開始分析過其生成代碼的過程,generate
內(nèi)部會調(diào)用genElement
用來解析el
,也就是AST
語法樹。我們來看一下genElement
的源碼:
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優(yōu)先級比v-if高, // 因為解析ast樹生成渲染函數(shù)代碼時,會先解析ast樹中涉及到v-for的屬性 // 然后再解析ast樹中涉及到v-if的屬性 // 而且genFor在會把el.forProcessed置為true,防止重復(fù)解析v-for相關(guān)屬性 } 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 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 } }
接下來依次看看genFor
和genIf
的函數(shù)源碼:
export function genFor (el, state , altGen, altHelper) { const exp = el.for const alias = el.alias const iterator1 = el.iterator1 ? `,${el.iterator1}` : '' const iterator2 = el.iterator2 ? `,${el.iterator2}` : '' el.forProcessed = true // avoid recursion return `${altHelper || '_l'}((${exp}),` + `function(${alias}${iterator1}${iterator2}){` + `return ${(altGen || genElement)(el, state)}` + //遞歸調(diào)用genElement '})' }
在我們的例子里,當他處理li
的ast
樹時,會先調(diào)用genElement
,處理到for
屬性時,此時forProcessed
為虛值,此時調(diào)用genFor
處理li
樹中的v-for
相關(guān)的屬性。然后再調(diào)用genElement
處理li
樹,此時因為forProcessed
在genFor
中已被標記為true
。因此genFor
不會被執(zhí)行,繼而執(zhí)行genIf
處理與v-if
相關(guān)的屬性。
export function genIf (el,state,altGen,altEmpty) { el.ifProcessed = true // avoid recursion // 調(diào)用genIfConditions主要處理el.ifConditions屬性 return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty) } function genIfConditions (conditions, state, altGen, altEmpty) { if (!conditions.length) { return altEmpty || '_e()' // _e用于生成空VNode } const condition = conditions.shift() if (condition.exp) { //condition.exp即v-if綁定值,例子中則為'index!==0' // 生成一段帶三目運算符的js代碼字符串 return `(${condition.exp})?${ genTernaryExp(condition.block) }:${ genIfConditions(conditions, state, altGen, altEmpty) }` } else { return `${genTernaryExp(condition.block)}` } // v-if with v-once should generate code like (a)?_m(0):_m(1) function genTernaryExp (el) { return altGen ? altGen(el, state) : el.once ? genOnce(el, state) : genElement(el, state) } }
參考 前端進階面試題詳細解答
最后,經(jīng)過codegen生成的js代碼如下:
function render() { with(this) { return _c('ul', _l((items), function (item, index) { return (index !== 0) ? _c('li') : _e() }), 0) } }
其中:
_c
: 調(diào)用createElement
去創(chuàng)建VNode
_l
:renderList
函數(shù),主要用來渲染列表_e
:createEmptyVNode
函數(shù),主要用來創(chuàng)建空VNode
總結(jié)
為什么v-for的優(yōu)先級比v-if的高?總結(jié)來說是編譯有三個過程,parse
->optimize
->codegen
。在codegen
過程中,會先解析AST
樹中的與v-for
相關(guān)的屬性,再解析與v-if
相關(guān)的屬性。除此之外,也可以知道Vue
對v-for
和v-if
是怎么處理的。
以上就是vue為什么v-for的優(yōu)先級比v-if高原理解析的詳細內(nèi)容,更多關(guān)于vue優(yōu)先級v-for v-if的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue自定義指令添加跟隨鼠標光標提示框v-tooltip方式
這篇文章主要介紹了vue自定義指令添加跟隨鼠標光標提示框v-tooltip方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10Vue3中Provide?/?Inject的實現(xiàn)原理分享
provide和inject主要為高階插件/組件庫提供用例,這篇文章主要給大家介紹了關(guān)于Vue3中Provide?/?Inject的實現(xiàn)原理,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-02-02vue2.0 和 animate.css的結(jié)合使用
animate.css是一款前端動畫庫,相似的有velocity-animate。這篇文章給大家介紹vue2.0 和 animate.css的結(jié)合使用,需要的朋友參考下吧2017-12-12vue+jquery+lodash實現(xiàn)滑動時頂部懸浮固定效果
這篇文章主要為大家詳細介紹了vue+jquery+lodash實現(xiàn)滑動時頂部懸浮固定效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-04-04Vue中JS動畫與Velocity.js的結(jié)合使用
這篇文章主要介紹了Vue中JS動畫與Velocity.js的結(jié)合使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-02-02vue中echarts關(guān)系圖動態(tài)增刪節(jié)點以及連線方式
這篇文章主要介紹了vue中echarts關(guān)系圖動態(tài)增刪節(jié)點以及連線方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07