vue原理Compile之optimize標(biāo)記靜態(tài)節(jié)點(diǎn)源碼示例
引言
optimize這一步的內(nèi)容好像不太多,但是非常重要,于是是一個(gè)更新性能優(yōu)化, 非常重要
先來看看 optimize 在什么位置,就在 parse 處理完之后,generate 之前
var ast = parse(template.trim(), options); if (options.optimize !== false) { optimize(ast, options); } var code = generate(ast, options);
上面這段代碼在函數(shù) baseCompile 中,如果想了解的,看這里 Compile - 從新建實(shí)例到 compile結(jié)束的主要流程
而 optimize 的作用是什么呢?
Vue官方注釋
優(yōu)化器的目標(biāo)
遍歷生成的模板AST樹,檢測純靜態(tài)的子樹,即永遠(yuǎn)不需要更改的DOM。
一旦我們檢測到這些子樹,我們可以:
1、把它們變成常數(shù),這樣我們就不需要了在每次重新渲染時(shí)為它們創(chuàng)建新的節(jié)點(diǎn)
2、在修補(bǔ)過程中完全跳過它們。
那是怎么做的呢?
給靜態(tài)ast節(jié)點(diǎn)設(shè)置屬性 static,當(dāng)節(jié)點(diǎn)時(shí)靜態(tài)是
el.static = true
下面就來看下源碼
function optimize(root, options) { if (!root) return // first pass: mark all non-static nodes. markStatic$1(root); // second pass: mark static roots. markStaticRoots(root); }
里面主要調(diào)用了兩個(gè)函數(shù),這兩個(gè)函數(shù)會(huì)分別分析
但是在此之前,我們先來看一個(gè)函數(shù),這個(gè)函數(shù)就是 判斷靜態(tài)節(jié)點(diǎn)的 主力函數(shù)
直接傳入 ast 節(jié)點(diǎn),各種組合判斷,然后給 ast 節(jié)點(diǎn)添加上 static 屬性
function isStatic(node) { // 文字表達(dá)式 if (node.type === 2) return false // 純文本 if (node.type === 3) return true return ( // 設(shè)置了 v-pre 指令,表示不用解析 node.pre || ( !node.hasBindings && // 沒有動(dòng)態(tài)綁定 ! node.if && !node.for && // 不存在v-if ,v-for 指令 ! ['slot','component'].indexOf(node.tag)>-1 && // 需要編譯的標(biāo)簽 isPlatformReservedTag(node.tag) && // 正常html 標(biāo)簽 ! isDirectChildOfTemplateFor(node) && Object.keys(node).every(isStaticKey) ) ) }
如果要判斷為靜態(tài)節(jié)點(diǎn),就要經(jīng)過下面7個(gè)條件的審判(把上面的代碼列了出來)
1 是否存在 v-pre
如果添加了指令 v-pre,那么 node.pre 為 true,表明所有節(jié)點(diǎn)都不用解析了
2 不能存在 node.hasBindings
當(dāng)節(jié)點(diǎn)有綁定 Vue屬性的時(shí)候,比如指令,事件等,node.hasBindings 會(huì)為 true
3 不能存在 node.if 和 node.for
同樣,當(dāng) 節(jié)點(diǎn)有 v-if 或者 v-for 的時(shí)候,node.if 或者 node.for 為true
4 節(jié)點(diǎn)名稱不能是slot 或者 component
因?yàn)檫@兩者是要?jiǎng)討B(tài)編譯的,不屬于靜態(tài)范疇
所以只要是 slot 或者 component 都不可能是靜態(tài)節(jié)點(diǎn)
5 isPlatformReservedTag(node.tag)
isPlatformReservedTag 是用于判斷該標(biāo)簽是否是正常的HTML 標(biāo)簽,有什么標(biāo)簽?zāi)兀?/p>
標(biāo)簽必須是正常HTML標(biāo)簽,如下
html,body,base,head,link,meta,style,title
address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section
div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul
a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby
s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video
embed,object,param,source,canvas,script,noscript,del,ins
caption,col,colgroup,table,thead,tbody,td,th,tr
button,datalist,section,form,input,label,legend,meter,optgroup,option
output,progress,select,textarea
details,dialog,menu,menuitem,summary
content,element,shadow,template,blockquote,iframe,tfoot
svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face
foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern
polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view
是不是挺多的,哈哈,尤大真厲害,估計(jì)收集了很多,我覺得應(yīng)該有用,就全放上來了
6 isDirectChildOfTemplateFor(node)
看下這個(gè)函數(shù)的源碼
function isDirectChildOfTemplateFor(node) { while (node.parent) { node = node.parent; if (node.tag !== 'template') { return false } if (node.for) { return true } } return false }
表明了節(jié)點(diǎn)父輩以上所有節(jié)點(diǎn)不能是 template 或者 帶有 v-for
7 Object.keys(node).every(isStaticKey)
isStaticKey是一個(gè)函數(shù),用于判斷傳入的屬性是否在下面的范圍內(nèi)
type,tag,attrsList,attrsMap,plain,parent,children,attrs
比如這樣的 ast
<div style="" ></div> { attrsList: [] attrsMap: {style: ""} children: [] parent: undefined plain: false tag: "div" type: 1 }
上面的 ast 的所有屬性通過 isStaticKey 判斷之后,都在上面列出的屬性范圍中,都是靜態(tài)屬性,所以這就是一個(gè)靜態(tài)節(jié)點(diǎn)
而當(dāng)你存在之外的其他屬性的時(shí)候,這個(gè)節(jié)點(diǎn)就不是靜態(tài)ast
然后下面就來看 optimize 中出現(xiàn)的兩個(gè)函數(shù)把
markStatic$1 和 markStaticRoot
標(biāo)記靜態(tài)節(jié)點(diǎn)
怎么標(biāo)記一個(gè)節(jié)點(diǎn)是否是靜態(tài)節(jié)點(diǎn)呢,就在 markStatic$1 中進(jìn)行處理
// 標(biāo)記節(jié)點(diǎn)是否是靜態(tài)節(jié)點(diǎn) function markStatic$1(node) { node.static = isStatic(node); if (node.type !== 1) return // 不要將組件插槽內(nèi)容設(shè)置為靜態(tài)。 // 這就避免了 // 1、組件無法更改插槽節(jié)點(diǎn) // 2、靜態(tài)插槽內(nèi)容無法熱加載 if ( // 正常 thml 標(biāo)簽 才往下處理,組件之類的就不可以 !isPlatformReservedTag(node.tag) && // 標(biāo)簽名是 slot 才往下處理 node.tag !== 'slot' && // 有 inline-tempalte 才往下處理 node.attrsMap['inline-template'] == null ) { return } // 遍歷所有孩子,如果孩子 不是靜態(tài)節(jié)點(diǎn),那么父親也不是靜態(tài)節(jié)點(diǎn) var l = node.children.length for (var i = 0;i < l; i++) { var child = node.children[i]; // 遞歸設(shè)置子節(jié)點(diǎn),子節(jié)點(diǎn)再調(diào)用子節(jié)點(diǎn) markStatic$1(child); if (!child.static) { node.static = false; } } if (node.ifConditions) { var c = node.ifConditions.length for (var j = 1; j < c; j++) { // block 是 節(jié)點(diǎn)的 ast var block = node.ifConditions[j].block; markStatic$1(block); if (!block.static) { node.static = false; } } } }
這個(gè)方法做了下面三種事情
1 isStatic 這個(gè)方法對(duì) ast 節(jié)點(diǎn)本身進(jìn)行初步判斷
進(jìn)行初步靜態(tài)節(jié)點(diǎn)判斷
2 判斷靜態(tài)節(jié)點(diǎn)的額外的處理
給節(jié)點(diǎn)本身判斷完是否靜態(tài)節(jié)點(diǎn)之后,需要做額外的處理,就是需要檢查所有的子孫節(jié)點(diǎn)
于是便會(huì)逐層遞歸子節(jié)點(diǎn),如果某子節(jié)點(diǎn)不是靜態(tài)節(jié)點(diǎn),那么父節(jié)點(diǎn)就不能是靜態(tài)節(jié)點(diǎn),但是并不是所有節(jié)點(diǎn)都會(huì)進(jìn)行特殊處理,是有條件的
節(jié)點(diǎn)類型是 1
- 類型 1 是 標(biāo)簽元素
- 類型 2 是 文字表達(dá)式
- 類型 3 是 純文本
3 是正常 html 標(biāo)簽,標(biāo)簽是 slot,存在 inline-template 屬性
1、必須是正常標(biāo)簽,也就是說自定義標(biāo)簽不需要再次處理
2、slot 會(huì)額外處理
3、有 inline-template 屬性也會(huì)額外處理
只有有一個(gè)滿足,就會(huì)進(jìn)行額外處理
疑點(diǎn)
你可以看到源碼中的最后一步
判斷 node.ifCondition,并且如果 ifCondition 中保存的節(jié)點(diǎn)不是靜態(tài)的話,那么這個(gè) node 也不是靜態(tài)節(jié)點(diǎn)
這個(gè)判斷就很讓我匪夷所思了
明明如果存在 v-if 的話,該節(jié)點(diǎn)在 一開始的 isStatic 中,就會(huì)被設(shè)置 node.static 為 false 了
為什么還要在末尾 再判斷一遍呢?
這里我覺得好像有點(diǎn)多余?反正我沒有想通 尤大 的想法啊啊啊啊啊,為了確保正確?
經(jīng)過這一步,所有的節(jié)點(diǎn),都會(huì)被添加上 static 屬性,節(jié)點(diǎn)是否靜態(tài),一看便知
標(biāo)記靜態(tài)根節(jié)點(diǎn)
// 標(biāo)記根節(jié)點(diǎn)是否是靜態(tài)節(jié)點(diǎn) function markStaticRoots(node) { if (node.type === 1) return // 要使一個(gè)節(jié)點(diǎn)符合靜態(tài)根的條件,它應(yīng)該有這樣的子節(jié)點(diǎn) // 不僅僅是靜態(tài)文本。否則,吊裝費(fèi)用將會(huì)增加 // 好處大于好處,最好總是保持新鮮。 if ( // 靜態(tài)節(jié)點(diǎn) node.static && // 有孩子 node.children.length && // 孩子有很多,或者第一個(gè)孩子不是純文本 ! (node.children.length === 1 && node.children[0].type === 3 ) ) { node.staticRoot = true; return } else { node.staticRoot = false; } if (node.children) { var l = node.children.length for (var i = 0; i < l; i++) { markStaticRoots( node.children[i] ); } } }
這個(gè)方法只會(huì)不斷的尋找 靜態(tài)的根節(jié)點(diǎn),應(yīng)該說是區(qū)域根節(jié)點(diǎn)吧,反正一個(gè)節(jié)點(diǎn)下面有馬仔節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)就算是根節(jié)點(diǎn)
遞歸他的所有子孫,看看誰是靜態(tài)根節(jié)點(diǎn),如果是靜態(tài)ast,就會(huì)被添加上 staticRoot 這個(gè)屬性
markStaticRoots 也是遞歸調(diào)用的,但是并不是會(huì)處理到所有節(jié)點(diǎn)
因?yàn)檎业揭粋€(gè)根節(jié)點(diǎn)是靜態(tài)根節(jié)點(diǎn)后,就不會(huì)遞歸處理他的子節(jié)點(diǎn)了
然后我們需要了解兩個(gè)問題
1、markStaticRoot 和 markStatic$1 的區(qū)別
2、判斷靜態(tài)根節(jié)點(diǎn)的依據(jù)是什么
markStaticRoots 和 markStatic$1 有什么區(qū)別?
找出靜態(tài)根節(jié)點(diǎn)才是性能優(yōu)化的最終作用者
markStatic$1 這個(gè)函數(shù)只是為 markStaticRoots 服務(wù)的,是為了先把每個(gè)節(jié)點(diǎn)都處理之后,更加方便快捷靜態(tài)根節(jié)點(diǎn),可以說是把功能分開,這樣處理的邏輯就更清晰了
先給所有的節(jié)點(diǎn)都劃分身份,之后處理靜態(tài)節(jié)點(diǎn)時(shí),只用找 那部分的根節(jié)點(diǎn)(區(qū)域負(fù)責(zé)人就好了)
當(dāng)然,上面都是我個(gè)人的理解,那么我的依據(jù)是什么呢?
markStatic$1 添加的 static 屬性,我全局搜索,并沒有在處理DOM和 生成 render上使用過
而 markStaticRoots 添加的 staticRoot ,在生成 render 上使用了
而且再 根據(jù) markStaticRoots 寫的功能邏輯 并 使用了 static 屬性進(jìn)行判斷
所以我認(rèn)為 markStatic$1 是為 markStaticRoots 服務(wù)的一個(gè)函數(shù)
被判斷為靜態(tài)根節(jié)點(diǎn)的條件是什么?
1該節(jié)點(diǎn)的所有子孫節(jié)點(diǎn)都是靜態(tài)節(jié)點(diǎn)
而 node.static = true 則表明了其所有子孫都是靜態(tài)的,否則上一步就被設(shè)置為 false 了
2必須存在子節(jié)點(diǎn)
3子節(jié)點(diǎn)不能只有一個(gè) 純文本節(jié)點(diǎn)
這一點(diǎn)我不太明白,為什么只有一個(gè)純文本子節(jié)點(diǎn)時(shí),這個(gè)點(diǎn)不能是靜態(tài)根節(jié)點(diǎn)?
注意:只有純文本子節(jié)點(diǎn)時(shí),他是靜態(tài)節(jié)點(diǎn),但是不是靜態(tài)根節(jié)點(diǎn)。靜態(tài)根節(jié)點(diǎn)是optimize 優(yōu)化的條件,沒有靜態(tài)根節(jié)點(diǎn),說明這部分不會(huì)被優(yōu)化
而 Vue 官方說明是,如果子節(jié)點(diǎn)只有一個(gè)純文本節(jié)點(diǎn),如果優(yōu)化的話,帶來的成本就比好處多了,所以就不優(yōu)化
那么我就疑惑了
為什么子節(jié)點(diǎn)只有是靜態(tài)文本時(shí),成本會(huì)大?
下面是我的個(gè)人探索的想法
首先,我們明確,優(yōu)化的好處是,減少DOM比對(duì),加速更新
而帶來的成本是什么呢?
1、維護(hù)靜態(tài)模板存儲(chǔ)對(duì)象
2、多層函數(shù)調(diào)用
現(xiàn)在我們來簡單解釋下上面兩種成本
1 維護(hù)靜態(tài)模板存儲(chǔ)對(duì)象
一開始的時(shí)候,所有的靜態(tài)根節(jié)點(diǎn) 都會(huì)被解析生成 VNode,并且被存在一個(gè)緩存對(duì)象中,就在 Vue.proto._staticTree 中
比如下面這個(gè)靜態(tài)模板
解析后被存了進(jìn)去
隨著靜態(tài)根節(jié)點(diǎn)的增加,這個(gè)存儲(chǔ)對(duì)象也會(huì)越來越大,那么占用的內(nèi)存就會(huì)越來越多
勢必要減少一些不必要的存儲(chǔ),所有只有純文本的靜態(tài)根節(jié)點(diǎn)就被排除了
2 多層函數(shù)調(diào)用
這個(gè)問題涉及到 render 和 靜態(tài) render 的合作
舉個(gè)例子
一個(gè)動(dòng)態(tài)跟靜態(tài)混合的模板
生成的 render 函數(shù)是這樣的
with(this) { return _c('div', [ _m(0), ( testStaticRender) ? _c('span') : _e() ]) }
看到 _m(0) 了嗎,這個(gè)函數(shù)就是去獲取靜態(tài)模板的
這樣,靜態(tài)模板的處理
就多了一個(gè) _m 函數(shù)的調(diào)用,加上初期涉及到了很多函數(shù)的處理,其中包括上一步的存儲(chǔ)
再者,既然純文本節(jié)點(diǎn)不做優(yōu)化
那么就是說更新時(shí)需要比對(duì)這部分嘍?
但是純文本的比對(duì),就是直接 比較字符串 是否相等而已啊
消耗簡直不要太小,那么這樣,我還有必要去維護(hù)多一個(gè)靜態(tài)模板緩存嗎?
綜上所述
只有純文本子節(jié)點(diǎn)最好不要當(dāng)做靜態(tài)模板處理
以上只是個(gè)人的意淫想法,如有不同意見可以提出
番外疑惑
我不禁疑惑到,難道只有一個(gè)普通標(biāo)簽子節(jié)點(diǎn)的時(shí)候,好處難道會(huì)大一些嗎?
可以看到模板放在了 staticRenderFns 上,做了靜態(tài)模板處理
結(jié)果論出發(fā)的話,可能消耗的確大一些吧哈哈哈
更新的時(shí)候,會(huì)比較 div 和 span 和 span 內(nèi)的純文本
以上就是vue原理Compile之optimize標(biāo)記靜態(tài)節(jié)點(diǎn)源碼示例的詳細(xì)內(nèi)容,更多關(guān)于Compile optimize標(biāo)記靜態(tài)節(jié)點(diǎn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue3項(xiàng)目中reset.scss模板使用導(dǎo)入
這篇文章主要為大家介紹了Vue3項(xiàng)目中reset.scss模板使用導(dǎo)入示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09Vue3.0導(dǎo)出數(shù)據(jù)為自定義樣式Excel的詳細(xì)實(shí)例
在許多的后臺(tái)系統(tǒng)中少不了導(dǎo)出Excel表格的功能,下面這篇文章主要給大家介紹了關(guān)于Vue3.0導(dǎo)出數(shù)據(jù)為自定義樣式Excel的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06Vue中的Object.defineProperty全面理解
這篇文章主要介紹了Vue中的Object.defineProperty全面理解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04Vue?2源碼閱讀?Provide?Inject?依賴注入詳解
這篇文章主要為大家介紹了Vue?2源碼閱讀?Provide?Inject?依賴注入詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08element-ui?tree?異步樹實(shí)現(xiàn)勾選自動(dòng)展開、指定展開、指定勾選功能
這篇文章主要介紹了element-ui?tree?異步樹實(shí)現(xiàn)勾選自動(dòng)展開、指定展開、指定勾選,項(xiàng)目中用到了vue的element-ui框架,用到了el-tree組件,由于數(shù)據(jù)量很大,使用了數(shù)據(jù)懶加載模式,即異步樹,需要的朋友可以參考下2022-08-08深入理解Vue-cli搭建項(xiàng)目后的目錄結(jié)構(gòu)探秘
本篇文章主要介紹了深入理解Vue-cli搭建項(xiàng)目后的目錄結(jié)構(gòu)探秘,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07