這篇文章主要為大家展示了“Ivy編譯器中增量DOM的示例分析”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“Ivy編譯器中增量DOM的示例分析”這篇文章吧。
專業(yè)領(lǐng)域包括網(wǎng)站設(shè)計、網(wǎng)站制作、商城網(wǎng)站建設(shè)、微信營銷、系統(tǒng)平臺開發(fā), 與其他網(wǎng)站設(shè)計及系統(tǒng)開發(fā)公司不同,創(chuàng)新互聯(lián)建站的整合解決方案結(jié)合了幫做網(wǎng)絡(luò)品牌建設(shè)經(jīng)驗和互聯(lián)網(wǎng)整合營銷的理念,并將策略和執(zhí)行緊密結(jié)合,為客戶提供全網(wǎng)互聯(lián)網(wǎng)整合方案。
作為“為大型前端項目”而設(shè)計的前端框架,Angular 其實有許多值得參考和學(xué)習(xí)的設(shè)計,本系列主要用于研究這些設(shè)計和功能的實現(xiàn)原理。本文圍繞 Angular 的核心功能 Ivy 編譯器,介紹其中的增量 DOM 設(shè)計。
在介紹前端框架的時候,我常常會介紹到模板引擎。對于模板引擎的渲染過程,像 Vue/React 這樣的框架里,使用了虛擬 DOM 這樣的設(shè)計。
在 Angular Ivy 編譯器中,并沒有使用虛擬 DOM,而且使用了增量 DOM。
在 Ivy 編譯器里,模板編譯后的產(chǎn)物與 View Engine 不一樣了,這是為了支持單獨編譯、增量編譯等能力。
比如,<span>My name is {{name}}</span>
這句模板代碼,在 Ivy 編譯器中編譯后的代碼大概長這個樣子:
// create mode if (rf & RenderFlags.Create) { elementStart(0, "span"); text(1); elementEnd(); } // update mode if (rf & RenderFlags.Update) { textBinding(1, interpolation1("My name is", ctx.name)); }
可以看到,相比于 View Engine 中的elementDef(0,null,null,1,'span',...),
,elementStart()
、elementEnd()
這些 API 顯得更加清爽,它們使用的便是增量 DOM 的設(shè)計。
虛擬 DOM 想必大家都已經(jīng)有所了解,它的核心計算過程包括:
用 JavaScript 對象模擬 DOM 樹,得到一棵虛擬 DOM 樹。
當(dāng)頁面數(shù)據(jù)變更時,生成新的虛擬 DOM 樹,比較新舊兩棵虛擬 DOM 樹的差異。
把差異應(yīng)用到真正的 DOM 樹上。
雖然虛擬 DOM 解決了頁面被頻繁更新和渲染帶來的性能問題,但傳統(tǒng)虛擬 DOM 依然有以下性能瓶頸:
在單個組件內(nèi)部依然需要遍歷該組件的整個虛擬 DOM 樹
在一些組件整個模版內(nèi)只有少量動態(tài)節(jié)點的情況下,這些遍歷都是性能的浪費
遞歸遍歷和更新邏輯容易導(dǎo)致 UI 渲染被阻塞,用戶體驗下降
針對這些情況,React 和 Vue 等框架也有更多的優(yōu)化,比如 React 中分別對 tree diff、component diff 以及 element diff 進行了算法優(yōu)化,同時引入了任務(wù)調(diào)度來控制狀態(tài)更新的計算和渲染。在 Vue 3.0 中,則將虛擬 DOM 的更新從以前的整體作用域調(diào)整為樹狀作用域,樹狀的結(jié)構(gòu)會帶來算法的簡化以及性能的提升。
而不管怎樣,虛擬 DOM 的設(shè)計中存在一個無法避免的問題:每個渲染操作分配一個新的虛擬 DOM 樹,該樹至少大到足以容納發(fā)生變化的節(jié)點,并且通常更大一些,這樣的設(shè)計會導(dǎo)致更多的一些內(nèi)存占用。當(dāng)大型虛擬 DOM 樹需要大量更新時,尤其是在內(nèi)存受限的移動設(shè)備上,性能可能會受到影響。
增量 DOM 的設(shè)計核心思想是:
在創(chuàng)建新的(虛擬)DOM 樹時,沿著現(xiàn)有的樹走,并在進行時找出更改。
如果沒有變化,則不分配內(nèi)存;
如果有,改變現(xiàn)有樹(僅在絕對必要時分配內(nèi)存)并將差異應(yīng)用到物理 DOM。
這里將(虛擬)放在括號中是因為,當(dāng)將預(yù)先計算的元信息混合到現(xiàn)有 DOM 節(jié)點中時,使用物理 DOM 樹而不是依賴虛擬 DOM 樹實際上已經(jīng)足夠快了。
與基于虛擬 DOM 的方法相比,增量 DOM 有兩個主要優(yōu)勢:
增量特性允許在渲染過程中顯著減少內(nèi)存分配,從而實現(xiàn)更可預(yù)測的性能
它很容易映射到基于模板的方法。控制語句和循環(huán)可以與元素和屬性聲明自由混合
增量 DOM 的設(shè)計由 Google 提出,同時他們也提供了一個開源庫 google/incremental-dom,它是一個用于表達和應(yīng)用 DOM 樹更新的庫。JavaScript 可用于提取、迭代數(shù)據(jù)并將其轉(zhuǎn)換為生成 HTMLElements 和 Text 節(jié)點的調(diào)用。
但新的 Ivy 引擎沒有直接使用它,而是實現(xiàn)了自己的版本。
Ivy 引擎基于增量 DOM 的概念,它與虛擬 DOM 方法的不同之處在于,diff 操作是針對 DOM 增量執(zhí)行的(即一次一個節(jié)點),而不是在虛擬 DOM 樹上執(zhí)行。基于這樣的設(shè)計,增量 DOM 與 Angular 中的臟檢查機制其實能很好地搭配。
增量 DOM 的 API 的一個獨特功能是它分離了標(biāo)簽的打開(elementStart
)和關(guān)閉(elementEnd
),因此它適合作為模板語言的編譯目標(biāo),這些語言允許(暫時)模板中的 HTML 不平衡(比如在單獨的模板中,打開和關(guān)閉的標(biāo)簽)和任意創(chuàng)建 HTML 屬性的邏輯。
在 Ivy 中,使用elementStart
和elementEnd
創(chuàng)建一個空的 Element 實現(xiàn)如下(在 Ivy 中,elementStart
和elementEnd
的具體實現(xiàn)便是??elementStart
和??elementEnd
):
export function ??element( index: number, name: string, attrsIndex?: number | null, localRefsIndex?: number ): void { ??elementStart(index, name, attrsIndex, localRefsIndex); ??elementEnd(); }
其中,??elementStart
用于創(chuàng)建 DOM 元素,該指令后面必須跟有??elementEnd()
調(diào)用。
export function ??elementStart( index: number, name: string, attrsIndex?: number | null, localRefsIndex?: number ): void { const lView = getLView(); const tView = getTView(); const adjustedIndex = HEADER_OFFSET + index; const renderer = lView[RENDERER]; // 此處創(chuàng)建 DOM 元素 const native = (lView[adjustedIndex] = createElementNode( renderer, name, getNamespace() )); // 獲取 TNode // 在第一次模板傳遞中需要收集匹配 const tNode = tView.firstCreatePass ? elementStartFirstCreatePass( adjustedIndex, tView, lView, native, name, attrsIndex, localRefsIndex) : tView.data[adjustedIndex] as TElementNode; setCurrentTNode(tNode, true); const mergedAttrs = tNode.mergedAttrs; // 通過推斷的渲染器,將所有屬性值分配給提供的元素 if (mergedAttrs !== null) { setUpAttributes(renderer, native, mergedAttrs); } // 將 className 寫入 RElement const classes = tNode.classes; if (classes !== null) { writeDirectClass(renderer, native, classes); } // 將 cssText 寫入 RElement const styles = tNode.styles; if (styles !== null) { writeDirectStyle(renderer, native, styles); } if ((tNode.flags & TNodeFlags.isDetached) !== TNodeFlags.isDetached) { // 添加子元素 appendChild(tView, lView, native, tNode); } // 組件或模板容器的任何直接子級,必須預(yù)先使用組件視圖數(shù)據(jù)進行猴子修補 // 以便稍后可以使用任何元素發(fā)現(xiàn)實用程序方法檢查元素 if (getElementDepthCount() === 0) { attachPatchData(native, lView); } increaseElementDepthCount(); // 對指令 Host 的處理 if (isDirectiveHost(tNode)) { createDirectivesInstances(tView, lView, tNode); executeContentQueries(tView, tNode, lView); } // 獲取本地名稱和索引的列表,并將解析的本地變量值按加載到模板中的相同順序推送到 LView if (localRefsIndex !== null) { saveResolvedLocalsInData(lView, tNode); } }
可以看到,在??elementStart
創(chuàng)建 DOM 元素的過程中,主要依賴于LView
、TView
和TNode
。
在 Angular Ivy 中,使用了LView
和TView.data
來管理和跟蹤渲染模板所需要的內(nèi)部數(shù)據(jù)。對于TNode
,在 Angular 中則是用于在特定類型的所有模板之間共享的特定節(jié)點的綁定數(shù)據(jù)(享元)。
??elementEnd()
則用于標(biāo)記元素的結(jié)尾:
export function ??elementEnd(): void {}
對于??elementEnd()
的詳細實現(xiàn)不過多介紹,基本上主要包括一些對 Class 和樣式中@input
等指令的處理,循環(huán)遍歷提供的tNode
上的指令、并將要運行的鉤子排入隊列,元素層次的處理等等。
在增量 DOM 中,每個組件都被編譯成一系列指令。這些指令創(chuàng)建 DOM 樹并在數(shù)據(jù)更改時就地更新它們。
Ivy 在運行時編譯一個組件的過程中,會創(chuàng)建模板解析相關(guān)指令:
export function compileComponentFromMetadata( meta: R3ComponentMetadata, constantPool: ConstantPool, bindingParser: BindingParser ): R3ComponentDef { // 其他暫時省略 // 創(chuàng)建一個 TemplateDefinitionBuilder,用于創(chuàng)建模板相關(guān)的處理 const templateBuilder = new TemplateDefinitionBuilder( constantPool, BindingScope.createRootScope(), 0, templateTypeName, null, null, templateName, directiveMatcher, directivesUsed, meta.pipes, pipesUsed, R3.namespaceHTML, meta.relativeContextFilePath, meta.i18nUseExternalIds); // 創(chuàng)建模板解析相關(guān)指令,包括: // 第一輪:創(chuàng)建模式,包括所有創(chuàng)建模式指令(例如解析偵聽器中的綁定) // 第二輪:綁定和刷新模式,包括所有更新模式指令(例如解析屬性或文本綁定) const templateFunctionExpression = templateBuilder.buildTemplateFunction(template.nodes, []); // 提供這個以便動態(tài)生成的組件在實例化時,知道哪些投影內(nèi)容塊要傳遞給組件 const ngContentSelectors = templateBuilder.getNgContentSelectors(); if (ngContentSelectors) { definitionMap.set("ngContentSelectors", ngContentSelectors); } // 生成 ComponentDef 的 consts 部分 const { constExpressions, prepareStatements } = templateBuilder.getConsts(); if (constExpressions.length > 0) { let constsExpr: o.LiteralArrayExpr|o.FunctionExpr = o.literalArr(constExpressions); // 將 consts 轉(zhuǎn)換為函數(shù) if (prepareStatements.length > 0) { constsExpr = o.fn([], [...prepareStatements, new o.ReturnStatement(constsExpr)]); } definitionMap.set("consts", constsExpr); } // 生成 ComponentDef 的 template 部分 definitionMap.set("template", templateFunctionExpression); }
可見,在組件編譯時,會被編譯成一系列的指令,包括const
、vars
、directives
、pipes
、styles
、changeDetection
等等,當(dāng)然也包括template
模板里的相關(guān)指令。最終生成的這些指令,會體現(xiàn)在編譯后的組件中,比如之前文章中提到的這樣一個Component
文件:
import { Component, Input } from "@angular/core"; @Component({ selector: "greet", template: "<div> Hello, {{name}}! </div>", }) export class GreetComponent { @Input() name: string; }
經(jīng)ngtsc
編譯后,產(chǎn)物包括該組件的.js
文件:
const i0 = require("@angular/core"); class GreetComponent {} GreetComponent.?cmp = i0.??defineComponent({ type: GreetComponent, tag: "greet", factory: () => new GreetComponent(), template: function (rf, ctx) { if (rf & RenderFlags.Create) { i0.??elementStart(0, "div"); i0.??text(1); i0.??elementEnd(); } if (rf & RenderFlags.Update) { i0.??advance(1); i0.??textInterpolate1("Hello ", ctx.name, "!"); } }, });
其中,elementStart()
、text()
、elementEnd()
、advance()
、textInterpolate1()
這些都是增量 DOM 相關(guān)的指令。在實際創(chuàng)建組件的時候,其template
模板函數(shù)也會被執(zhí)行,相關(guān)的指令也會被執(zhí)行。
正因為在 Ivy 中,是由組件來引用著相關(guān)的模板指令。如果組件不引用某個指令,則我們的 Angular 中永遠不會使用到它。因為組件編譯的過程發(fā)生在編譯過程中,因此我們可以根據(jù)引用到指令,來排除未引用的指令,從而可以在 Tree-shaking 過程中,將未使用的指令從包中移除,這便是增量 DOM 可樹搖的原因。
以上是“Ivy編譯器中增量DOM的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
分享標(biāo)題:Ivy編譯器中增量DOM的示例分析
文章路徑:http://m.newbst.com/article48/jegeep.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供全網(wǎng)營銷推廣、網(wǎng)站設(shè)計公司、軟件開發(fā)、ChatGPT、網(wǎng)站收錄、企業(yè)建站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)