引言
optimize的內容雖然不多,但十分重要,它是一個更新性能優化,現在來說說:
首先找到optimize位置,就在 parse 處理完之后,generate 之前
var ast = parse(template.trim(), options); if (options.optimize !== false) { optimize(ast, options); } var code = generate(ast, options);
上面這段代碼在函數 baseCompile 中,如果想了解的,看這里Compile - 從新建實例到 compile結束的主要流程
而 optimize 的作用是什么呢?
Vue官方注釋
優化器的目標
遍歷生成的模板AST樹,檢測純靜態的子樹,即永遠不需要更改的DOM。
一旦我們檢測到這些子樹,我們可以:
1、將它們變成常數,就是為了在每次重新渲染時不需要再為它們創建新的節點
2、在修補過程中完全跳過它們。
那是怎么做的呢?
給靜態ast節點設置屬性 static,當節點時靜態是
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); }
里面主要調用了兩個函數,這兩個函數會分別分析
但是在此之前,我們先來看一個函數,這個函數就是 判斷靜態節點的 主力函數
直接傳入 ast 節點,各種組合判斷,然后給 ast 節點添加上 static 屬性
function isStatic(node) { // 文字表達式 if (node.type === 2) return false // 純文本 if (node.type === 3) return true return ( // 設置了 v-pre 指令,表示不用解析 node.pre || ( !node.hasBindings && // 沒有動態綁定 ! node.if && !node.for && // 不存在v-if ,v-for 指令 ! ['slot','component'].indexOf(node.tag)>-1 && // 需要編譯的標簽 isPlatformReservedTag(node.tag) && // 正常html 標簽 ! isDirectChildOfTemplateFor(node) && Object.keys(node).every(isStaticKey) ) ) }
如果要判斷為靜態節點,就要經過下面7個條件的審判(把上面的代碼列了出來)
1 是否存在 v-pre
如果添加了指令 v-pre,那么 node.pre 為 true,表明所有節點都不用解析了
2 不能存在 node.hasBindings
當節點有綁定 Vue屬性的時候,比如指令,事件等,node.hasBindings 會為 true
3 不能存在 node.if 和 node.for
同樣,當 節點有 v-if 或者 v-for 的時候,node.if 或者 node.for 為true
4 節點名稱不能是slot 或者 component
因為這兩者是要動態編譯的,不屬于靜態范疇
所以只要是 slot 或者 component 都不可能是靜態節點
5 isPlatformReservedTag(node.tag)
isPlatformReservedTag 是用于判斷該標簽是否是正常的HTML 標簽,有什么標簽呢?
標簽必須是正常HTML標簽,如下
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
是不是挺多的,哈哈,尤大真厲害,估計收集了很多,我覺得應該有用,就全放上來了
6 isDirectChildOfTemplateFor(node)
看下這個函數的源碼
function isDirectChildOfTemplateFor(node) { while (node.parent) { node = node.parent; if (node.tag !== 'template') { return false } if (node.for) { return true } } return false }
表明了節點父輩以上所有節點不能是 template 或者 帶有 v-for
7 Object.keys(node).every(isStaticKey)
isStaticKey是一個函數,用于判斷傳入的屬性是否在下面的范圍內
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 判斷之后,會在上面逐一列出的屬性范圍中,且是靜態屬性,因此,這是靜態節點
而當你存在之外的其他屬性的時候,這個節點就不是靜態ast
然后下面就來看 optimize 中出現的兩個函數把
markStatic$1 和 markStaticRoot
標記靜態節點
怎么標記一個節點是否是靜態節點呢,就在 markStatic$1 中進行處理
// 標記節點是否是靜態節點 function markStatic$1(node) { node.static = isStatic(node); if (node.type !== 1) return // 不要將組件插槽內容設置為靜態。 // 這就避免了 // 1、組件無法更改插槽節點 // 2、靜態插槽內容無法熱加載 if ( // 正常 thml 標簽 才往下處理,組件之類的就不可以 !isPlatformReservedTag(node.tag) && // 標簽名是 slot 才往下處理 node.tag !== 'slot' && // 有 inline-tempalte 才往下處理 node.attrsMap['inline-template'] == null ) { return } // 遍歷所有孩子,如果孩子 不是靜態節點,那么父親也不是靜態節點 var l = node.children.length for (var i = 0;i < l; i++) { var child = node.children[i]; // 遞歸設置子節點,子節點再調用子節點 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 是 節點的 ast var block = node.ifConditions[j].block; markStatic$1(block); if (!block.static) { node.static = false; } } } }
這個方法做了下面三種事情
1 isStatic 這個方法對 ast 節點本身進行初步判斷
進行初步靜態節點判斷
2 判斷靜態節點的額外的處理
給節點本身判斷完是否靜態節點之后,就需要檢查所有的子孫節點。后面就是逐層遞歸子節點,如果某子節點不是靜態節點,那么父節點就不能是靜態節點,現在就說說這樣的節點處理,其實并不是所有節點都會進行特殊處理,是有條件的
節點類型是 1
類型 1 是 標簽元素
類型 2 是 文字表達式
類型 3 是 純文本
3 是正常 html 標簽,標簽是 slot,存在 inline-template 屬性
1、必須是正常標簽,也就是說自定義標簽不需要再次處理
2、slot 會額外處理
3、有 inline-template 屬性也會額外處理
只有有一個滿足,就會進行額外處理
疑點
你可以看到源碼中的最后一步
判斷 node.ifCondition,并且如果 ifCondition 中保存的節點不是靜態的話,那么這個 node 也不是靜態節點
這個判斷就很讓我匪夷所思了
明明如果存在 v-if 的話,該節點在 一開始的 isStatic 中,就會被設置 node.static 為 false 了
為什么還要在末尾 再判斷一遍呢?
這里多余的一步,但為保證正常運行還是加入在里面。
經過這一步,所有的節點,都會被添加上 static 屬性,節點是否靜態,一看便知
標記靜態根節點
// 標記根節點是否是靜態節點 function markStaticRoots(node) { if (node.type === 1) return // 要使一個節點符合靜態根的條件,它應該有這樣的子節點 // 不僅僅是靜態文本。否則,吊裝費用將會增加 // 好處大于好處,最好總是保持新鮮。 if ( // 靜態節點 node.static && // 有孩子 node.children.length && // 孩子有很多,或者第一個孩子不是純文本 ! (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] ); } } }
這樣也只是可以尋找 靜態的根節點,應該說是區域根節點吧,反正一個節點下面有馬仔節點,這個節點就算是根節點。
遞歸他的所有子孫,由此判斷哪個是靜態根節點,如果是靜態ast,就會被添加上 staticRoot 這個屬性
markStaticRoots 也是遞歸調用的,但是并不是會處理到所有節點
因為找到一個根節點是靜態根節點后,就不會遞歸處理他的子節點了
然后我們需要了解兩個問題
1、markStaticRoot 和 markStatic$1 的區別
2、判斷靜態根節點的依據是什么
markStaticRoots 和 markStatic$1 有什么區別?
找出靜態根節點才是性能優化的最終作用者
markStatic$1 這個函數只是為 markStaticRoots 服務的,是為了先把每個節點都處理之后,更加方便快捷靜態根節點,可以說是把功能分開,這樣處理的邏輯就更清晰了
劃分好之后,就只用找 部分的根節點(區域負責人就好了)
這個就是因為markStatic$1 添加的 static 屬性,我全局搜索,并沒有在處理DOM和 生成 render上使用過
而 markStaticRoots 添加的 staticRoot ,在生成 render 上使用了
而且再 根據 markStaticRoots 寫的功能邏輯 并 使用了 static 屬性進行判斷
所以我認為 markStatic$1 是為 markStaticRoots 服務的一個函數
被判斷為靜態根節點的條件是什么?
1該節點的所有子孫節點都是靜態節點
而 node.static = true 則表明了其所有子孫都是靜態的,否則上一步就被設置為 false 了
2必須存在子節點
3子節點不能只有一個 純文本節點
簡單來說就是這個只有一個純文本子節點時,這個點不能是靜態根節點。且只有純文本子節點時,他是靜態節點,但是不是靜態根節點。靜態根節點是optimize 優化的條件,沒有靜態根節點,說明這部分不會被優化
注意在Vue 官方說明中寫的是,如果子節點只有一個純文本節點,如果優化的話,帶來的成本就比好處多了,因此,選擇不優化
但為什么子節點只有是靜態文本時,成本會大?現在來說說我個人的看法:
首先,優化不僅可以減少DOM比對,而且可以提升加速更新
而帶來的成本是什么呢?
1、維護靜態模板存儲對象
2、多層函數調用
現在我們來簡單解釋下上面兩種成本
1 維護靜態模板存儲對象
首先所有的靜態根節點 都會被解析生成 VNode,并且被存在一個緩存對象中,就在 Vue.proto._staticTree 中,比如下面這個靜態模板
之后就解析,且存進去
但逐漸存儲內容越多,越大,占用空間就成為問題。減少儲存成為必須,所有只有純文本的靜態根節點就被排除了
2 多層函數調用
這個問題涉及到 render 和 靜態 render 的合作
舉個例子
一個動態跟靜態混合的模板
生成的 render 函數是這樣的
with(this) { return _c('div', [ _m(0), ( testStaticRender) ? _c('span') : _e() ]) }
看到 _m(0) 了嗎,這個函數就是去獲取靜態模板的。現在說說,態模板的處理,就多了一個 _m 函數的調用,加上初期涉及到了很多函數的處理,其中包括上一步的存儲,之后再去,既然純文本節點不做優化,這樣就說明更新時需要比對這部分嘍?
但是純文本的比對,就是直接 比較字符串 是否相等而已啊
消耗簡直不要太小,那么這樣,我還有必要去維護多一個靜態模板緩存嗎?
其實總來說就是:
只有純文本子節點最好不要當做靜態模板處理
可以看到模板放在了 staticRenderFns 上,做了靜態模板處理
看到了吧。消耗大了吧.
更新的時候,會比較 div 和 span 和 span 內的純文本
vue原理Compile之optimize標記靜態節點源碼示例的詳細內容講述完畢,歡迎大家看后續精彩內容。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/127837.html
摘要:一旦我們檢測到這些子樹,我們可以把它們變成常數,這樣我們就不需要了在每次重新渲染時為它們創建新的節點在修補過程中完全跳過它們。否則,吊裝費用將會增加好處大于好處,最好總是保持新鮮。 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理白話版終于到了要講白話的時候了 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究...
摘要:問簡述一下的編譯過程先上一張圖大致看一下整個流程從上圖中我們可以看到是從后開始進行中整體邏輯分為三個部分解析器將模板字符串轉換成優化器對進行靜態節點標記,主要用來做虛擬的渲染優化代碼生成器使用生成函數代碼字符串開始前先解釋一下抽象 20190215問 簡述一下Vue.js的template編譯過程? 先上一張圖大致看一下整個流程showImg(https://image-static....
摘要:超出此時間則渲染錯誤組件。元素節點總共有種類型,為表示是普通元素,為表示是表達式,為表示是純文本。 實戰 - 插件 form-validate {{ error }} ...
摘要:具體可以查看抽象語法樹。而則是帶緩存的編譯器,同時以及函數會被轉換成對象。會用正則等方式解析模板中的指令等數據,形成語法樹。是將語法樹轉化成字符串的過程,得到結果是的字符串以及字符串。里面的節點與父節點的結構類似,層層往下形成一棵語法樹。 寫在前面 因為對Vue.js很感興趣,而且平時工作的技術棧也是Vue.js,這幾個月花了些時間研究學習了一下Vue.js源碼,并做了總結與輸出。 文...
閱讀 547·2023-03-27 18:33
閱讀 732·2023-03-26 17:27
閱讀 630·2023-03-26 17:14
閱讀 591·2023-03-17 21:13
閱讀 521·2023-03-17 08:28
閱讀 1801·2023-02-27 22:32
閱讀 1292·2023-02-27 22:27
閱讀 2178·2023-01-20 08:28