導航:首頁 > 源碼編譯 > vue項目編譯後代碼邏輯改變了

vue項目編譯後代碼邏輯改變了

發布時間:2025-01-09 22:58:27

⑴ 淺談vue3的編譯優化

編譯優化:編譯器將模版編譯為渲染函數的過程中,盡可能地提取關鍵信息,並以此指導生成最優代碼的過程。

優化的方向:盡可能地區分動態內容和靜態內容,並針對不同的內容採用不同的優化策略

1.動態節點收集與補丁標志1.1傳統diff演算法的問題

比對新舊兩棵虛擬DOM樹的時候,總是要按照虛擬DOM的層級結構「一層一層」地遍歷

<divid="foo"><pclass="bar">{{text}}</p></div>

上面這段代碼中,當響應式數據text值發生變化的時候,最高效的更新方式是直接設置p標簽的文本內容

傳統Diff演算法做不到如此高效,當text值發生變化的時候,會產生一顆新的虛擬DOM樹,對比新舊虛擬DOM過程如下:

對比div節點,以及該節點的屬性和子節點

對比p節點,以及該節點的屬性和子節點

對比p節點的文本子節點,如果文本子節點的內容變了,則更新,否則什麼都不做

可以發現,有很多無意義的對比操作。

總結:

傳統diff演算法的問題:無法利用編譯時提取到的任何關鍵信息,導致渲染器在運行時不會去做相關的優化。

vue3的編譯器會將編譯得到的關鍵信息「附著」在它生成的虛擬DOM上,傳遞給渲染器,執行「快捷路徑」。

1.2Block與PatchFlags

傳統Diff演算法無法避免新舊虛擬DOM樹間無用的比較操作,是因為運行時得不到足夠的關鍵信息,從而無法區分動態內容和靜態內容。換句話說,只要運行時能夠區分動態內容和靜態內容,就可以實現極簡的優化策略

舉個例子:

<div><div>foo</div><p>{{bar}}</p></div>

只有{{bar}}是動態的內容。理想情況下,當數據bar的值變化時,只需要更新p標簽的文本節點即可。為了實現這個目標,需要提供信息給運行時

//傳統虛擬DOM描述constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar},]}//編譯優化後constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar,patchFlag:1},//這是動態節點]}

可以發現,虛擬節點多了一個額外的屬性,即patchFlag(補丁標志),存在該屬性,就認為是動態節點

patchFlag(補丁標志)可以理解為一系列的數字標記,含義如下

constPatchFlags={TEXT:1,//代表節點有動態的textContentCLASS:2,//代表元素有動態的class綁定STYLE:3//其他。。。}

可以在虛擬節點的創建階段,把它的動態子節點提取出來,並存儲到該虛擬節點的dynamicChildren數組中

constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar,patchFlag:1},//這是動態節點],//將children中的動態節點提取到dynamicChildren數組中dynamicChildren:[{tag:'p',children:ctx.bar,patchFlag:PatchFlags.TEXT}]}

Block定義:帶有dynamicChildren屬性的虛擬節點稱為「塊」,即(Block)

一個Block本質上也是一個虛擬DOM,比普通的虛擬節點多處一個用來存儲動態節點的dynamicChildren屬性。(能夠收集所有的動態子代節點)

渲染器的更新操作會以Block為維度。當渲染器在更新一個Block時,會忽略虛擬節點的children數組,直接找到dynamicChildren數組,並只更新該數組中的動態節點。跳過了靜態內容,只更新動態內容。同時,由於存在對應的補丁標志,也能夠做到靶向更新。

Block節點有哪些:模版根節點、帶有v-for、v-if/v-else-if/v-else等指令的節點

1.3收集動態節點

編譯器生成的渲染函數代碼中,不會直接包含用來描述虛擬節點的數據結構,而是包含著用來創建虛擬DOM節點的輔助函數,如下

render(){returncreateVNode('div',{id:'foo'},[createVNode('p',null,'text')])}functioncreateVNode(tag,props,children){constkey=props&&props.keyprops&&deleteprops.key//省略部分代碼return{tag,props,children,key}}

createVNode的返回值是一個虛擬DOM節點

舉個例子:

<divid="foo"><pclass="bar">{{bar}}</p></div>

上面模版生成帶有補丁標志的渲染函數如下:

render(){returncreateVNode('div',{id:'foo'},[createVNode('p',{class:'bar'},text,PatchFlags.TEXT)])}

怎麼將根節點變成一個Block,如何將動態子代節點收集到該Block的dynamicChildren數組中?

可以發現,在渲染函數內,對createVNode函數的調用是層層嵌套結構,執行順序是內層先執行,外層再執行,當外層createVNode函數執行時,內層的createVNode函數已經執行完畢了。因此,為了讓外層Block節點能夠收集到內層動態節點,需要一個棧結構的數據來臨時存儲內層的動態節點。代碼實現如下:

//動態節點constdynamicChildrenStack=[]//當前動態節點集合letcurrentDynamicChildren=null//openBlock用來創建一個新的動態節點集合,並將該集合壓入棧中functionopenBlock(){dynamicChildrenStack.push((currentDynamicChildren=[]))}//closeBlock用來通過openBlock創建的動態節點集合從棧中彈出functioncloseBlock(){currentDynamicChildren=dynamicChildrenStack.pop()}

然後調整createVNode函數

<div><div>foo</div><p>{{bar}}</p></div>0

接著調整

<div><div>foo</div><p>{{bar}}</p></div>11.4.渲染器的運行時支持

傳統的節點更新方式如下:

<div><div>foo</div><p>{{bar}}</p></div>2

優化後的更新方式,直接對比動態節點

<div><div>foo</div><p>{{bar}}</p></div>3

存在對應的補丁標志,可以針對性地完成靶向更新

<div><div>foo</div><p>{{bar}}</p></div>42.Block樹

除了模版的根節點是Block外,帶有結構化指令的節點,如:v-if、v-for,也都應該是Block

2.1帶有v-if指令的節點<div><div>foo</div><p>{{bar}}</p></div>5

假設只有最外層的div標簽會作為Block,那麼變數foo的值為true還是false,block收集到的動態節點都是一樣的,如下:

<div><div>foo</div><p>{{bar}}</p></div>6

這意味著,在Diff階段不會更新。顯然,foo不同值下,一個是section,一個是div,是不同標簽,是需要更新的。

再舉個例子:

<div><div>foo</div><p>{{bar}}</p></div>7

一樣會導致更新失敗

問題在於:dynamicChildren收集的動態節點是忽略虛擬DOM樹層級的,結構化指令會導致更新前後模版的結構發生變化,即模版結構不穩定

解決方法:讓帶有v-if/v-else-if/v-else等結構化指令的節點也作為Block即可,如下所示

<div><div>foo</div><p>{{bar}}</p></div>8<div><div>foo</div><p>{{bar}}</p></div>9

在Diff過程中,渲染器根據key值區分,使用新的Block替換舊的Block

2.2帶有v-for指令的節點

帶有v-for指令的節點也會讓虛擬DOM樹變得不穩定

例子:

//傳統虛擬DOM描述constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar},]}0

list的值由[1,2]變成[1]

更新前後對應的Block樹如下:

//傳統虛擬DOM描述constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar},]}1

更新前後,動態節點數量不一致,無法進行diff操作(diff操作的前提是:操作的節點必須是同層級節點,dynamicChildren不一定是同層級的)

解決方法:讓v-for指令的標簽也作為Block角色,保證虛擬DOM樹具有穩定的結構,無論v-for在運行時怎樣變化。如下:

//傳統虛擬DOM描述constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar},]}2

由於v-for指令渲染的是一個片段,所以類型用Fragment

2.3Fragment的穩定性//傳統虛擬DOM描述constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar},]}3

發現Fragment本身收集的動態節點存在結構是不穩定的情況

結構不穩定:指更新前後一個block的dynamicChildren數組中收集的動態節點的數量或順序不一致

這種情況無法直接進行靶向更新

解決方法:回退到傳統虛擬DOM的Diff手段,即直接使用Fragment的children而非dynamicChildren來進行Diff操作

Fragment的子節點仍然可以是由Block組成的數組

//傳統虛擬DOM描述constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar},]}4

當Fragment的子節點更新時,就可以恢復優化模式

有穩定的Fragment嗎?如下:

//傳統虛擬DOM描述constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar},]}5

穩定的Fragment,可以使用優化模式

vue3模版中的多個根節點,也是穩定的Fragment

//傳統虛擬DOM描述constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar},]}63.靜態提升

減少更新時創建虛擬DOM帶來的性能開銷和內存佔用

如:

//傳統虛擬DOM描述constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar},]}7

沒有靜態提升時,渲染函數是:

//傳統虛擬DOM描述constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar},]}8

響應式數據title變化後,整個渲染函數會重新執行

把純靜態的節點提升到渲染函數之外

//傳統虛擬DOM描述constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar},]}9

響應式數據title變化後,不會重新創建靜態的虛擬節點

註:靜態提升是以樹為單位的

包含動態綁定的節點本身不會被提升,但是該節點上的靜態屬性是可以被提升的

//編譯優化後constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar,patchFlag:1},//這是動態節點]}0

可以減少創建虛擬DOM產生的開銷以及內存佔用

4.預字元串化

基於靜態提升,進一步採用預字元串化優化。

//編譯優化後constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar,patchFlag:1},//這是動態節點]}1

採用靜態提升優化策略後

//編譯優化後constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar,patchFlag:1},//這是動態節點]}2

採用預字元串化將這些靜態節點序列化為字元串,並生成一個Static類型的VNode

//編譯優化後constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar,patchFlag:1},//這是動態節點]}3

優勢:

大塊的靜態內容可以通過innerHTML設置,在性能上有一定優勢

減少創建虛擬節點產生的性能開銷

減少內存佔用

5.緩存內聯事件處理函數//編譯優化後constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar,patchFlag:1},//這是動態節點]}4//編譯優化後constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar,patchFlag:1},//這是動態節點]}5

每次重新渲染時,都會為Com組件創建一個全新的props對象。同時,props對象中onChange屬性的值也會是全新的函數。造成額外的性能開銷

//編譯優化後constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar,patchFlag:1},//這是動態節點]}66.v-once

v-once可以對虛擬DOM進行緩存

//編譯優化後constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar,patchFlag:1},//這是動態節點]}7

由於節點被緩存,意味著更新前後的虛擬節點不會發生變化,因此也就不需要這些被緩存的虛擬節點參與Diff操作了。編譯後的結果如下:

//編譯優化後constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar,patchFlag:1},//這是動態節點]}8

v-once包裹的動態節點不會被父級Block收集,因此不會參與Diff操作

v-once指令通常用於不會發生改變的動態綁定中,例如綁定一個常量

//編譯優化後constvnode={tag:'div',children:[{tag:'div',children:'foo'},{tag:'p',children:ctx.bar,patchFlag:1},//這是動態節點]}9

v-once帶來的性能提升

避免組件更新時重新創建虛擬DOM帶來的性能開銷。因為虛擬DOM被緩存了,所以更新時無需重新創建

避免無用的Diff開銷。因為被v-once標記的虛擬DOM樹不會被父級Block節點收集

7.總結

1.vue3提出了Block的概念,利用Block樹及補丁標志

2.靜態提升:可以減少更新時創建虛擬DOM產生的性能開銷和內存佔用

3.預字元串化:在靜態提升的基礎上,對靜態節點進行字元串化。這樣做能夠減少創建虛擬節點產生的性能開銷以及內存佔用

4.緩存內聯事件處理函數:避免造成不必要的組件更新

5.v-once指令:緩存全部或部分虛擬節點,能夠避免組件更新時重新創建虛擬DOM帶來的性能開銷,也可以避免無用的Diff操作

原文:https://juejin.cn/post/7101859824203202568
閱讀全文

與vue項目編譯後代碼邏輯改變了相關的資料

熱點內容
androidmirrors 瀏覽:464
光點伺服器地址 瀏覽:265
php7教程pdf 瀏覽:386
攝像頭多怎麼用伺服器裝硬碟 瀏覽:178
kmeans聚類演算法權重距離公式 瀏覽:688
安卓怎麼下載信聊 瀏覽:936
天狐app怎麼樣 瀏覽:893
程序員佛系頭像 瀏覽:705
貓架app是干什麼的 瀏覽:516
建行app怎麼繳社保 瀏覽:101
flooding演算法 瀏覽:414
福州地區吃飯用什麼app 瀏覽:952
榮耀大文件夾名稱大全 瀏覽:980
空白文件夾集中 瀏覽:16
php查看瀏覽器版本 瀏覽:636
怎麼用搜索引擎搜奧運會伺服器 瀏覽:210
蘋果手機使用軟體怎麼伺服器繁忙 瀏覽:586
長河全文在什麼app可以看 瀏覽:597
廣東dns伺服器雲伺服器 瀏覽:45
java等待喚醒 瀏覽:754