【Vue原理】Compile - 源碼版 之 optimize 標記靜態節點

寫文章不容易,點個讚唄兄弟


專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧
研究基於 Vue版本 【2.5.17】

若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧html

【Vue原理】Compile - 源碼版 之 optimize 標記靜態節點 node

compile 三大步驟,parse 咱們已經講完了canvas

如今到了第二步了,optimize這一步的內容好像不太多,可是很是重要,因而是一個更新性能優化, 很是重要緩存

先來看看 optimize 在什麼位置,就在 parse 處理完以後,generate 以前ruby

var ast = parse(template.trim(), options);



if (options.optimize !== false) {

    optimize(ast, options);
}



var code = generate(ast, options);

上面這段代碼在函數 baseCompile 中,若是想了解的,看這裏 Compile - 重新建實例到 compile結束的主要流程 性能優化

而 optimize 的做用是什麼呢?ide

Vue官方註釋


優化器的目標
遍歷生成的模板AST樹,檢測純靜態的子樹,即永遠不須要更改的DOM。
一旦咱們檢測到這些子樹,咱們能夠:
一、把它們變成常數,這樣咱們就不須要了在每次從新渲染時爲它們建立新的節點
二、在修補過程當中徹底跳過它們。

那是怎麼作的呢?svg

給靜態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 都不多是靜態節點

5isPlatformReservedTag(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

是否是挺多的,哈哈,尤大真厲害,估計收集了不少,我以爲應該有用,就全放上來了

6isDirectChildOfTemplateFor(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

7Object.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


    // 不要將組件插槽內容設置爲靜態。
    // 這就避免了
    // 一、組件沒法更改插槽節點
    // 二、靜態插槽內容沒法熱加載
    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 是 純文本

二、是正常 html 標籤,標籤是 slot,存在 inline-template 屬性

一、必須是正常標籤,也就是說自定義標籤不須要再次處理

二、slot 會額外處理

三、有 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 也是遞歸調用的,可是並非會處理到全部節點

由於找到一個根節點是靜態根節點後,就不會遞歸處理他的子節點了

而後咱們須要瞭解兩個問題

一、markStaticRoot 和 markStatic$1 的區別

二、判斷靜態根節點的依據是什麼

一、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 維護靜態模板存儲對象

一開始的時候,全部的靜態根節點 都會被解析生成 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 內的純文本,須要比較三遍

因此乾脆選擇 靜態處理算了哈哈哈


最後

鑑於本人能力有限,不免會有疏漏錯誤的地方,請你們多多包涵,若是有任何描述不當的地方,歡迎後臺聯繫本人,有重謝

公衆號

相關文章
相關標籤/搜索