目錄
增量 DOM
增量DOM vs 虛擬DOM
Ivy 中的增量DOM
增量 DOM 元素创建
组件创建与增量 DOM 指令
结束语
首頁 web前端 js教程 Angular學習之淺析Ivy編譯器中的增量DOM

Angular學習之淺析Ivy編譯器中的增量DOM

Feb 23, 2022 am 11:07 AM
angular

這篇文章進行來學習Angular框架,帶大家了解一下Ivy編譯器中的增量DOM,希望對大家有幫助!

Angular學習之淺析Ivy編譯器中的增量DOM

作為「為大型前端專案」而設計的前端框架,Angular 其實有許多值得參考和學習的設計,本系列主要用於研究這些設計和功能的實現原理。本文圍繞著 Angular 的核心功能 Ivy 編譯器,介紹其中的增量 DOM 設計。 【相關教學推薦:《angular教學》】

在介紹前端框架的時候,我常常會介紹到模板引擎。對於模板引擎的渲染過程,像 Vue/React 這樣的框架裡,使用了虛擬 DOM 這樣的設計。

在 Angular Ivy 編譯器中,並沒有使用虛擬 DOM,而且使用了增量 DOM。

增量 DOM

在 Ivy 編譯器裡,模板編譯後的產物與 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 的設計。

增量DOM vs 虛擬DOM

虛擬DOM 想必大家都已經有所了解,它的核心計算過程包括:

  • 用JavaScript 對象模擬DOM 樹,得到一棵虛擬DOM 樹。

  • 當頁面資料變更時,產生新的虛擬 DOM 樹,比較新舊兩棵虛擬 DOM 樹的差異。

  • 把差異應用到真正的 DOM 樹上。

雖然虛擬DOM 解決了頁面被頻繁更新和渲染帶來的效能問題,但傳統虛擬DOM 依然有以下效能瓶頸:

  • 在單個元件內部仍需要遍歷該元件的整個虛擬DOM 樹
  • 在某些元件整個模版內只有少量動態節點的情況下,這些遍歷都是效能的浪費
  • 遞迴遍歷和更新邏輯容易導致UI 渲染被阻塞,使用者體驗下降

針對這些情況,React 和Vue 等框架也有更多的最佳化,例如React 中分別對tree diff、component diff 以及element diff 進行了演算法優化,同時引入了任務調度來控制狀態更新的計算和渲染。在 Vue 3.0 中,則將虛擬 DOM 的更新從先前的整體作用域調整為樹狀作用域,樹狀的結構會帶來演算法的簡化以及效能的提升。

而不管怎樣,虛擬DOM 的設計中存在一個無法避免的問題:每個渲染操作分配一個新的虛擬DOM 樹,該樹至少大到足以容納發生變化的節點,並且通常更大一些,這樣的設計會導致更多的一些記憶體佔用。當大型虛擬 DOM 樹需要大量更新時,尤其是在記憶體受限的行動裝置上,效能可能會受到影響。

增量DOM 的設計核心思想是:

  • 在創建新的(虛擬)DOM 樹時,沿著現有的樹走,並在進行時找出更改。

  • 如果沒有變化,則不分配記憶體;

  • #如果有,改變現有樹(僅在絕對必要時分配記憶體)並將差異應用到物理DOM。

這裡將(虛擬)放在括號中是因為,當將預先計算的元資訊混合到現有DOM 節點中時,使用實體DOM 樹而不是依賴虛擬DOM 樹實際上已經夠快了。

與基於虛擬DOM 的方法相比,增量DOM 有兩個主要優勢:

  • 增量特性允許在渲染過程中顯著減少記憶體分配,從而實現更可預測的效能
  • 它很容易對應到基於模板的方法。控制語句和循環可以與元素和屬性聲明自由混合

增量DOM 的設計由Google 提出,同時他們也提供了一個開源庫google/incremental-dom#,它是一個用於表達和應用DOM 樹更新的函式庫。 JavaScript 可用於擷取、迭代資料並將其轉換為產生 HTMLElements 和 Text 節點的呼叫。

但新的 Ivy 引擎沒有直接使用它,而是實作了自己的版本。

Ivy 中的增量DOM

Ivy 引擎是基於增量DOM 的概念,它與虛擬DOM 方法的不同之處在於,diff 操作是針對DOM 增量執行的(即一次一個節點),而不是在虛擬DOM 樹上執行。基於這樣的設計,增量 DOM 與 Angular 中的髒檢查機制其實也能很好地搭配。

增量 DOM 元素创建

增量 DOM 的 API 的一个独特功能是它分离了标签的打开(elementStart)和关闭(elementEnd),因此它适合作为模板语言的编译目标,这些语言允许(暂时)模板中的 HTML 不平衡(比如在单独的模板中,打开和关闭的标签)和任意创建 HTML 属性的逻辑。

在 Ivy 中,使用elementStartelementEnd创建一个空的 Element 实现如下(在 Ivy 中,elementStartelementEnd的具体实现便是ɵɵelementStartɵɵelementEnd):

export function ɵɵelement(
  index: number,
  name: string,
  attrsIndex?: number | null,
  localRefsIndex?: number
): void {
  ɵɵelementStart(index, name, attrsIndex, localRefsIndex);
  ɵɵelementEnd();
}
登入後複製

其中,ɵɵelementStart用于创建 DOM 元素,该指令后面必须跟有ɵɵelementEnd()调用。

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];
  // 此处创建 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);
  }

  // 组件或模板容器的任何直接子级,必须预先使用组件视图数据进行猴子修补
  // 以便稍后可以使用任何元素发现实用程序方法检查元素
  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创建 DOM 元素的过程中,主要依赖于LViewTViewTNode

在 Angular Ivy 中,使用了LViewTView.data来管理和跟踪渲染模板所需要的内部数据。对于TNode,在 Angular 中则是用于在特定类型的所有模板之间共享的特定节点的绑定数据(享元)。

ɵɵelementEnd()则用于标记元素的结尾:

export function ɵɵelementEnd(): void {}
登入後複製

对于ɵɵelementEnd()的详细实现不过多介绍,基本上主要包括一些对 Class 和样式中@input等指令的处理,循环遍历提供的tNode上的指令、并将要运行的钩子排入队列,元素层次的处理等等。

组件创建与增量 DOM 指令

在增量 DOM 中,每个组件都被编译成一系列指令。这些指令创建 DOM 树并在数据更改时就地更新它们。

Ivy 在运行时编译一个组件的过程中,会创建模板解析相关指令:

export function compileComponentFromMetadata(
  meta: R3ComponentMetadata,
  constantPool: ConstantPool,
  bindingParser: BindingParser
): R3ComponentDef {
  // 其他暂时省略

  // 创建一个 TemplateDefinitionBuilder,用于创建模板相关的处理
  const templateBuilder = new TemplateDefinitionBuilder(
      constantPool, BindingScope.createRootScope(), 0, templateTypeName, null, null, templateName,
      directiveMatcher, directivesUsed, meta.pipes, pipesUsed, R3.namespaceHTML,
      meta.relativeContextFilePath, meta.i18nUseExternalIds);

  // 创建模板解析相关指令,包括:
  // 第一轮:创建模式,包括所有创建模式指令(例如解析侦听器中的绑定)
  // 第二轮:绑定和刷新模式,包括所有更新模式指令(例如解析属性或文本绑定)
  const templateFunctionExpression = templateBuilder.buildTemplateFunction(template.nodes, []);

  // 提供这个以便动态生成的组件在实例化时,知道哪些投影内容块要传递给组件
  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 转换为函数
    if (prepareStatements.length > 0) {
      constsExpr = o.fn([], [...prepareStatements, new o.ReturnStatement(constsExpr)]);
    }
    definitionMap.set("consts", constsExpr);
  }

  // 生成 ComponentDef 的 template 部分
  definitionMap.set("template", templateFunctionExpression);
}
登入後複製

可见,在组件编译时,会被编译成一系列的指令,包括constvarsdirectivespipesstyleschangeDetection等等,当然也包括template模板里的相关指令。最终生成的这些指令,会体现在编译后的组件中,比如之前文章中提到的这样一个Component文件:

import { Component, Input } from "@angular/core";

@Component({
  selector: "greet",
  template: "<div> Hello, {{name}}! </div>",
})
export class GreetComponent {
  @Input() name: string;
}
登入後複製

ngtsc编译后,产物包括该组件的.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 相关的指令。在实际创建组件的时候,其template模板函数也会被执行,相关的指令也会被执行。

正因为在 Ivy 中,是由组件来引用着相关的模板指令。如果组件不引用某个指令,则我们的 Angular 中永远不会使用到它。因为组件编译的过程发生在编译过程中,因此我们可以根据引用到指令,来排除未引用的指令,从而可以在 Tree-shaking 过程中,将未使用的指令从包中移除,这便是增量 DOM 可树摇的原因。

结束语

现在,我们已经知道在 Ivy 中,是通过编译器将模板编译为template渲染函数,其中会将对模板的解析编译成增量 DOM 相关的指令。其中,在elementStart()执行时,我们可以看到会通过createElementNode()方法来创建 DOM。实际上,增量 DOM 的设计远不止只是创建 DOM,还包括变化检测等各种能力,关于具体的渲染过程,我们会在下一讲中进行介绍。

更多编程相关知识,请访问:编程教学!!

以上是Angular學習之淺析Ivy編譯器中的增量DOM的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

聊聊Angular中的元資料(Metadata)和裝飾器(Decorator) 聊聊Angular中的元資料(Metadata)和裝飾器(Decorator) Feb 28, 2022 am 11:10 AM

這篇文章繼續Angular的學習,帶大家了解Angular中的元數據和裝飾器,簡單了解一下他們的用法,希望對大家有幫助!

如何在Ubuntu 24.04上安裝Angular 如何在Ubuntu 24.04上安裝Angular Mar 23, 2024 pm 12:20 PM

Angular.js是一種可自由存取的JavaScript平台,用於建立動態應用程式。它允許您透過擴展HTML的語法作為模板語言,以快速、清晰地表示應用程式的各個方面。 Angular.js提供了一系列工具,可協助您編寫、更新和測試程式碼。此外,它還提供了許多功能,如路由和表單管理。本指南將討論在Ubuntu24上安裝Angular的方法。首先,您需要安裝Node.js。 Node.js是一個基於ChromeV8引擎的JavaScript運行環境,可讓您在伺服器端執行JavaScript程式碼。要在Ub

一文探究Angular中的服務端渲染(SSR) 一文探究Angular中的服務端渲染(SSR) Dec 27, 2022 pm 07:24 PM

你知道 Angular Universal 嗎?可以幫助網站提供更好的 SEO 支援哦!

angular學習之詳解狀態管理器NgRx angular學習之詳解狀態管理器NgRx May 25, 2022 am 11:01 AM

這篇文章帶大家深入了解angular的狀態管理器NgRx,介紹一下NgRx的使用方法,希望對大家有幫助!

Angular + NG-ZORRO快速開發一個後台系統 Angular + NG-ZORRO快速開發一個後台系統 Apr 21, 2022 am 10:45 AM

這篇文章跟大家分享一個Angular實戰,了解一下angualr 結合 ng-zorro 如何快速開發一個後台系統,希望對大家有幫助!

淺析angular中怎麼使用monaco-editor 淺析angular中怎麼使用monaco-editor Oct 17, 2022 pm 08:04 PM

angular中怎麼使用monaco-editor?以下這篇文章記錄下最近的一次業務中用到的 m​​onaco-editor 在 angular 中的使用,希望對大家有幫助!

如何使用PHP和Angular進行前端開發 如何使用PHP和Angular進行前端開發 May 11, 2023 pm 04:04 PM

隨著網路的快速發展,前端開發技術也不斷改進與迭代。 PHP和Angular是兩種廣泛應用於前端開發的技術。 PHP是一種伺服器端腳本語言,可以處理表單、產生動態頁面和管理存取權限等任務。而Angular是一種JavaScript的框架,可以用來開發單一頁面應用程式和建構元件化的網頁應用程式。本篇文章將介紹如何使用PHP和Angular進行前端開發,以及如何將它們

淺析Angular中的獨立組件,看看怎麼使用 淺析Angular中的獨立組件,看看怎麼使用 Jun 23, 2022 pm 03:49 PM

這篇文章帶大家了解Angular中的獨立元件,看看怎麼在Angular中建立一個獨立元件,怎麼在獨立元件中導入已有的模組,希望對大家有幫助!

See all articles