溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Angular中的變化檢測實例分析

發(fā)布時間:2022-02-14 10:45:48 來源:億速云 閱讀:144 作者:iii 欄目:web開發(fā)

這篇文章主要介紹“Angular中的變化檢測實例分析”的相關(guān)知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強(qiáng),希望這篇“Angular中的變化檢測實例分析”文章能幫助大家解決問題。

Angular中的變化檢測實例分析

變化檢測是前端框架中很有趣的一部分內(nèi)容,各個前端的框架也都有自己的一套方案,一般情況下我們不太需要過多的了解變化檢測,因為框架已經(jīng)幫我們完成了大部分的工作。不過隨著我們深入的使用框架,我們會發(fā)現(xiàn)我們很難避免的要去了解變化檢測,了解變化檢測可以幫助我們更好的理解框架、排查錯誤、進(jìn)行性能優(yōu)化等等。

什么是變化檢測 ?

簡單的來說,變化檢測就是通過檢測視圖與狀態(tài)之間的變化,在狀態(tài)發(fā)生了變化后,幫助我們更新視圖,這種將視圖和我們的數(shù)據(jù)同步的機(jī)制就叫變化檢測。

Angular中的變化檢測實例分析

變化檢測觸發(fā)時機(jī)

我們了解了什么是變化檢測,那何時觸發(fā)變化檢測呢?我們可以看看下面這兩個簡單的Demo

Demo1:

一個計數(shù)器組件,點擊按鈕Count會一直加 1

@Component({
  selector: "app-counter",
  template: `
    Count:{{ count }}
    <br />
    <button (click)="increase()">Increase</button>
  `,
})
export class CounterComponent {
  count = 0;
 
  constructor() {}
 
  increase() {
    this.count = this.count + 1;
  }
}

Demo2:

一個Todo List的組件,通過Http獲取數(shù)據(jù)后渲染到頁面

  @Component({
    selector: "app-todos",
    template: ` <li *ngFor="let item of todos">{{ item.titme }}</li> `,
  })
  export class TodosComponent implements OnInit {
    public todos: TodoItem[] = [];

    constructor(private http: HttpClient) {}

    ngOnInit() {
      this.http.get<TodoItem[]>("/api/todos").subscribe((todos: TodoItem[]) => {
        this.todos = todos;
      });
    }
  }

從上面的兩個 Demo 中我們發(fā)現(xiàn),在兩種情況下觸發(fā)了變化檢測:

  • 點擊事件發(fā)生時

  • 通過 http 請求遠(yuǎn)程數(shù)據(jù)時

仔細(xì)思考下,這兩種觸發(fā)的方式有什么共同點呢? 我們發(fā)現(xiàn)這兩種方式都是異步操作,所以我們可以得出一個結(jié)論: 只要發(fā)生了異步操作,Angular 就會認(rèn)為有狀態(tài)可能發(fā)生變化了,然后就會進(jìn)行變化檢測。

這個時候可能大家會想到  setTimeout  setInterval   ,是的,它們同樣也會觸發(fā)變化檢測。

@Component({
  selector: "app-counter",
  template: `
    Count:{{ count }}
    <br />
    <button (click)="increase()">Increase</button>
  `,
})
export class CounterComponent implements OnInit {
  count = 0;

  constructor() {}
  
  ngOnInit(){
    setTimeout(()=>{
       this.count= 10;
    });
  }

  increase() {
    this.count = this.count + 1;
  }
}

簡而言之,如果發(fā)生以下事件之一,Angular 將觸發(fā)變化檢測:

  • 任何瀏覽器事件(click、keydown 等)

  • setInterval()  和  setTimeout()

  • HTTP 通過  XMLHttpRequest  進(jìn)行請求

Angular 如何訂閱異步事件執(zhí)行變化檢測?

剛才我們了解到,只要發(fā)生了異步操作,Angular 就會進(jìn)行變化檢測,那 Angular 又是如何訂閱到異步事件的狀態(tài),從而觸發(fā)變化檢測的呢?這里我們就要聊一聊 zone.js 了。

Zone.js

Zone.js 提供了一種稱為 ** 區(qū)域(Zone) ** 的機(jī)制,用于封裝和攔截瀏覽器中的異步活動、它還提供 異步生命周期的鉤子統(tǒng)一的異步錯誤處理機(jī)制。

Zone.js 是通過 Monkey Patching(猴子補(bǔ)?。?/strong> 的方式來對瀏覽器中的常見方法和元素進(jìn)行攔截,例如 setTimeoutHTMLElement.prototype.onclick 。Angular 在啟動時會利用 zone.js 修補(bǔ)幾個低級瀏覽器 API,從而實現(xiàn)異步事件的捕獲,并在捕獲時間后調(diào)用變化檢測。

下面用一段簡化的代碼來模擬一下替換 setTimeout 的過程:

function setTimeoutPatch() {
  // 存儲原始的setTimeout
  var originSetTimeout = window['setTimeout'];
  // 對瀏覽器原生方法的包裹封裝
  window.setTimeout = function () {
      return global['zone']['setTimeout'].apply(global.zone, arguments);
  };
  // 創(chuàng)建包裹方法,提供給上面重寫后的setTimeout使用?
  Zone.prototype['setTimeout'] = function (fn, delay) {
    // 先調(diào)用原始方法
    originSetTimeout.apply(window, arguments);
    // 執(zhí)行完原始方法后就可以做其他攔截后需要進(jìn)行的操作了
    ...
   };
}

NgZone

Zone.js 提供了一個全局區(qū)域,可以被 fork 和擴(kuò)展以進(jìn)一步封裝/隔離異步行為,Angular 通過創(chuàng)建一個fork并使用自己的行為擴(kuò)展它,通常來說, 在 Angular APP 中,每個 Task 都會在 Angular 的 Zone 中運(yùn)行,這個 Zone 被稱為  NgZone 。一個 Angular APP 中只存在一個 Angular Zone, 而變更檢測只會由運(yùn)行于這個 ** **NgZone** ** 中的異步操作觸發(fā) 。

簡單的理解就是: Angular 通過 Zone.js 創(chuàng)建了一個自己的區(qū)域并稱之為 NgZone,Angular 應(yīng)用中所有的異步操作都運(yùn)行在這個區(qū)域中。

變化檢測是如何工作的?

我們了解 Angular 的核心是 組件化 ,組件的嵌套會使得最終形成一棵 組件樹 。

Angular中的變化檢測實例分析

Angular 在生成組件的同時,還會為每一個組件生成一個變化檢測器 changeDetector ,用來記錄組件的數(shù)據(jù)變化狀態(tài),由于一個 Component 會對應(yīng)一個 changeDetector ,所以changeDetector 同樣也是一個樹狀結(jié)構(gòu)的組織。

Angular中的變化檢測實例分析

在組件中我們可以通過注入 ChangeDetectorRef  來獲取組件的 changeDetector

@Component({
  selector: "app-todos",
  ...
})
export class TodosComponent{
  constructor(cdr: ChangeDetectorRef) {}
}

我們在創(chuàng)建一個 Angular 應(yīng)用 后,Angular 會同時創(chuàng)建一個 ApplicationRef  的實例,這個實例代表的就是我們當(dāng)前創(chuàng)建的這個 Angular 應(yīng)用的實例。 ApplicationRef 創(chuàng)建的同時,會訂閱 ngZone 中的 onMicrotaskEmpty  事件,在所有的微任務(wù)完成后調(diào)用所有的視圖的detectChanges()  來執(zhí)行變化檢測。

下是簡化的代碼:

class ApplicationRef {
  // ViewRef 是繼承于 ChangeDetectorRef 的
  _views: ViewRef[] = [];
  constructor(private _zone: NgZone) {
    this._zone.onMicrotaskEmpty.subscribe({
      next: () => {
        this._zone.run(() => {
          this.tick();
        });
      },
    });
  }

  // 執(zhí)行變化檢測
  tick() {
    for (let view of this._views) {
      view.detectChanges();
    }
  }
}

單向數(shù)據(jù)流

什么是單向數(shù)據(jù)流?

剛才我們說了每次觸發(fā)變化檢測,都會從根組件開始,沿著整棵組件樹從上到下的執(zhí)行每個組件的變更檢測,默認(rèn)情況下,直到最后一個葉子 Component 組件完成變更檢測達(dá)到穩(wěn)定狀態(tài)。在這個過程中,一但父組件完成變更檢測以后,在下一次事件觸發(fā)變更檢測之前,它的子孫組件都不允許去更改父組件的變化檢測相關(guān)屬性狀態(tài)的,這就是單向數(shù)據(jù)流。

我們看一個示例:

@Component({
  selector: "app-parent",
  template: `
    {{ title }}
    <app-child></app-child>
  `, 
})
export class ParentComponent {
  title = "我的父組件";
}

@Component({
  selector: "app-child",
  template: ``, 
})
export class ChildComponent implements AfterViewInit {
  constructor(private parent: ParentComponent) {}

  ngAfterViewInit(): void {
    this.parent.title = "被修改的標(biāo)題";
  }
}

Angular中的變化檢測實例分析

為什么出現(xiàn)這個錯誤呢?

這是因為我們違反了單向數(shù)據(jù)流,ParentComponent 完成變化檢測達(dá)到穩(wěn)定狀態(tài)后,ChildComponent 又改變了 ParentComponent 的數(shù)據(jù)使得 ParentComponent 需要再次被檢查,這是不被推薦的數(shù)據(jù)處理方式。在開發(fā)模式下,Angular 會進(jìn)行二次檢查,如果出現(xiàn)上述情況,二次檢查就會報錯: ExpressionChangedAfterItHasBeenCheckedError ,在生產(chǎn)環(huán)境中,則只會執(zhí)行一次檢查。

并不是在所有的生命周期去調(diào)用都會報錯,我們把剛才的示例修改一下:

@Component({
  selector: "app-child",
  template: ``, 
})
export class ChildComponent implements OnInit {
  constructor(private parent: ParentComponent) {}

  ngOnInit(): void {
    this.parent.title = "被修改的標(biāo)題";
  }
}

修改后的代碼運(yùn)行正常,這是為什么呢?這里要說一下Angular檢測執(zhí)行的順序:

  • 更新所有子子組件綁定的屬性

  • 調(diào)用所有子組件生命周期的鉤子 OnChanges, OnInit, DoCheck ,AfterContentInit

  • 更新當(dāng)前組件的DOM

  • 調(diào)用子組件的變換檢測

  • 調(diào)用所有子組件的生命周期鉤子 ngAfterViewInit

ngAfterViewInit 是在變化檢測之后執(zhí)行的,在執(zhí)行變化檢測后我們更改了父組件的數(shù)據(jù),在Angular執(zhí)行開發(fā)模式下的第二次檢查時,發(fā)現(xiàn)與上一次的值不一致,所以報錯,而ngOnInit 的執(zhí)行在變化檢測之前,所以一切正常。

這里提一下AngularJS,AngularJS采用的是雙向數(shù)據(jù)流,錯綜復(fù)雜的數(shù)據(jù)流使得它不得不多次檢查,使得數(shù)據(jù)最終趨向穩(wěn)定。理論上,數(shù)據(jù)可能永遠(yuǎn)不穩(wěn)定。AngularJS的策略是,臟檢查超過10次,就認(rèn)為程序有問題,不再進(jìn)行檢查。

Angular中的變化檢測實例分析

變化檢測的性能

剛才我們聊了變化檢測的工作流程,接下來我想說的是變化檢測的性能, 默認(rèn)情況下,當(dāng)我們的組件中某個值發(fā)生了變化觸發(fā)了變化檢測,那么Angular會從上往下檢查所有的組件。 不過Angular對每個組件進(jìn)行更改檢測的速度非??欤驗樗梢允褂?內(nèi)聯(lián)緩存 在幾毫秒內(nèi)執(zhí)行數(shù)千次檢查,其中內(nèi)聯(lián)緩存可生成對 VM 友好代碼。

盡管 Angular 進(jìn)行了大量優(yōu)化,但是遇到了大型應(yīng)用,變化檢測的性能仍然會下降,所以我們還需要用一些其他的方式來優(yōu)化我們的應(yīng)用。

變化檢測的策略

Angular 提供了兩種運(yùn)行變更檢測的策略:

  • Default

  • OnPush

Default 策略

默認(rèn)情況下,Angular 使用 ChangeDetectionStrategy.Default 變更檢測策略,每次事件觸發(fā)變化檢測(如用戶事件、計時器、XHR、promise 等)時,此默認(rèn)策略都會從上到下檢查組件樹中的每個組件。這種對組件的依賴關(guān)系不做任何假設(shè)的保守檢查方式稱為 臟檢查 ,這種策略在我們應(yīng)用組件過多時會對我們的應(yīng)用產(chǎn)生性能的影響。

Angular中的變化檢測實例分析

OnPush 策略

Angular 還提供了一種 OnPush 策略,我們可以修改組件裝飾器的 changeDetection 來更改變化檢測的策略

@Component({
    selector: 'app-demo',
    // 設(shè)置變化檢測的策略
    changeDetection: ChangeDetectionStrategy.OnPush,
    template: ...
})
export class DemoComponent {
    ...
}

設(shè)置為 OnPush 策略后,Angular 每次觸發(fā)變化檢測后會跳過該組件和該組件的所以子組件變化檢測

Angular中的變化檢測實例分析

OnPush模式下變化檢測流程

OnPush 策略下,只有以下這幾種情況才會觸發(fā)組件的變化檢測:

  • 輸入值(@Input)更改

  • 當(dāng)前組件或子組件之一觸發(fā)了事件

  • 手動觸發(fā)變化檢測

  • 使用 async 管道后, observable 值發(fā)生了變化

輸入值(@Input)更改

在默認(rèn)的變更檢測策略中,Angular 將在 @Input() 數(shù)據(jù)發(fā)生更改或修改時執(zhí)行變化檢測,使用該 OnPush 時,傳入 @Input()  的值 必須是一個新的引用 才會觸發(fā)變化檢測。

JavaScript有兩種數(shù)據(jù)類型,值類型和引用類型,值類型包括:number、string、boolean、null、undefined,引用類型包括:Object、Arrary、Function,值類型每次賦值都會分配新的空間,而引用類型比如Object,直接修改屬性是引用是不會發(fā)生變化的,只有賦一個新的對象才會改變引用。

var a= 1;
var b = a;
b = 2;
console.log(a==b); // false

var obj1 = {a:1};
var obj2 = obj1;
obj2.a = 2;
console.log(obj1); // {a:2}
console.log(obj1 === obj2); //true

obj2= {...obj1};
console.log(obj1 === obj2); //false

當(dāng)前組件或子組件之一觸發(fā)了事件

如果 OnPush 組件或其子組件之一觸發(fā)事件,例如 click,則將觸發(fā)變化檢測(針對組件樹中的所有組件)。

需要注意的是在 OnPush 策略中,以下操作不會觸發(fā)變化檢測:

  • setTimeout()

  • setInterval()

  • Promise.resolve().then()

  • this.http.get('...').subscribe()

手動觸發(fā)變化檢測

有三種手動觸發(fā)更改檢測的方法:

  • **detectChanges(): ** 它會觸發(fā)當(dāng)前組件和子組件的變化檢測

  • markForCheck(): 它不會觸發(fā)變化檢測,但是會把當(dāng)前的OnPush組件和所以的父組件為OnPush的組件 ** 標(biāo)記為需要檢測狀態(tài)** ,在當(dāng)前或者下一個變化檢測周期進(jìn)行檢測

  • ApplicationRef.tick() : 它會根據(jù)組件的變化檢測策略,觸發(fā)整個應(yīng)用程序的更改檢測

Angular中的變化檢測實例分析

可以通過 在線Demo ,更直觀的了解這幾種觸發(fā)變化檢測的方式

使用 async 管道

內(nèi)置的 AsyncPipe 訂閱一個 observable 并返回它發(fā)出的最新值。

每次發(fā)出新值時的內(nèi)部 AsyncPipe 調(diào)用 markForCheck

private _updateLatestValue(async: any, value: Object): void {
  if (async === this._obj) {
    this._latestValue = value;
    this._ref.markForCheck();
  }
}

減少變化檢測次數(shù)

剛才我們聊了變化檢測的策略,我們可以使用 OnPush 的策略來優(yōu)化我們的應(yīng)用,那么這就夠了嗎? 在我們實際的開發(fā)中還會有很多的場景,我們需要通過一些其他的方式來繼續(xù)優(yōu)化我們的應(yīng)用。

場景1:

假如我們在實現(xiàn)一個回車搜索的功能:

@Component({
  selector: "app-enter",
  template: `<input #input type="text" />`,
})
export class EnterComponent implements AfterViewInit {
  @ViewChild("input", { read: ElementRef })
  private inputElementRef: any;

  constructor() {}

  ngAfterViewInit(): void {
    this.inputElementRef.nativeElement.addEventListener(
      "keydown",
      (event: KeyboardEvent) => {
        const keyCode = event.which || event.keyCode;
        if (keyCode === 13) {
          this.search();
        }
      }
    );
  }

  search() {
    // ...
  }
}

大家從上面的示例中可以發(fā)現(xiàn)什么問題呢?

我們知道事件會觸發(fā)Angular的變化檢測,在示例中綁定 keydown 事件后,每一次鍵盤輸入都會觸發(fā)變化檢測,而這些變化檢測大多數(shù)都是多余的檢測,只有當(dāng)按鍵為 Enter 時,才需要真正的進(jìn)行變化檢測。

在這種情況下,我們就可以利用 NgZone.runOutsideAngular()  來減少變化檢測的次數(shù)。

@Directive({
    selector: '[enter]'
})
export class ThyEnterDirective implements OnInit {
    @Output() enter = new EventEmitter();

    constructor(private ngZone: NgZone, private elementRef: ElementRef<HTMLElement>) {}

    ngOnInit(): void {
        // 包裹代碼將運(yùn)行在Zone區(qū)域之外
        this.ngZone.runOutsideAngular(() => {
            this.elementRef.nativeElement.addEventListener('keydown', (event: KeyboardEvent) => {
                const keyCode = event.which || event.keyCode;
                if (keyCode === 13) {
                    this.ngZone.run(() => {
                        this.enter.emit(event);
                    });
                }
            });
        });
    }
}

場景2:

假如我們使用 WebSocket 將大量數(shù)據(jù)從后端推送到前端,則相應(yīng)的前端組件應(yīng)僅每 10 秒更新一次。在這種情況下,我們可以通過調(diào)用 detach() 和手動觸發(fā)它來停用更改檢測detectChanges()

constructor(private cdr: ChangeDetectorRef) {
    cdr.detach(); // 停用變化檢測
    setInterval(() => {
      this.cdr.detectChanges(); // 手動觸發(fā)變化檢測
    }, 10 * 1000);
  }

當(dāng)然使用 ngZone.runOutsideAngular()  也可以處理這種場景。

脫離 Zone.js 開發(fā)

之前我們說了Angular 可以自動幫我們進(jìn)行變化檢測,這主要是基于Zone.js來實現(xiàn),那么很多人潛意識會任務(wù)Zone.js 就是 Angular 是一部分,Angular的 應(yīng)用程序必須基于Zone.js,其實不然,如果我們對應(yīng)用有極高的性能要求時,我們可以選擇移除 Zone.js,移除Zone.js 將會提升應(yīng)用的性能和打包的體積,不過帶來的后果就是我們需要主要去調(diào)用變化檢測。

如何移除 Zone.js?

Angular中的變化檢測實例分析

手動調(diào)用變化檢測

在 Ivy 之后,我們有一些新的API可以更方便的調(diào)用變化檢測

Angular中的變化檢測實例分析

**?markDirty: ** 標(biāo)記一個組件為 dirty 狀態(tài) (需要重新渲染) 并將在未來某個時間點安排一個變更檢測

?detectChanges: 因為某些效率方面的原因,內(nèi)部文檔不推薦使用  ?detectChanges  而推薦使用  ?markDirty , ?detectChanges 會觸發(fā)組件以子組件的變更檢測。

移除后的性能

移除Zone.js后變化檢測由應(yīng)用自己來控制,極大的減少了不必要的變化檢測次數(shù),同時打包后的提及也減少了 36k

移除前:

Angular中的變化檢測實例分析

移除后:

Angular中的變化檢測實例分析

測試與變化檢測

組件綁定

我們先來看一個組件綁定的例子:

Angular中的變化檢測實例分析

按我們正常開發(fā)組件的想法,當(dāng)看到這個示例的時候一定認(rèn)為這個Case是Ok的,但是在運(yùn)行測試后我們發(fā)現(xiàn)這個Case失敗了。

在生產(chǎn)環(huán)境中,當(dāng) Angular 創(chuàng)建一個組件,就會自動進(jìn)行變更檢測。 但是在測試中,**TestBed.createComponent()** 并不會進(jìn)行變化檢測,需要我們手動觸發(fā)。

修改一下上面的Case:

origin-url0.00KB

origin-url0.00KB

從上面的示例中可以了解到,我們必須通過調(diào)用 fixture.detectChanges() 來告訴 TestBed 執(zhí)行數(shù)據(jù)綁定。

如果我們在測試中動態(tài)改變了綁定值,同樣也需要調(diào)用 fixture.detectChanges()

it("should update title", () => {
    component.title = 'Test Title';
    fixture.detectChanges();
    const h2 = fixture.nativeElement.querySelector("h2");
    expect(h2.textContent).toContain('Test Title');
});

自動變更檢測

我們發(fā)現(xiàn)寫測試過程中需要頻繁的調(diào)用 fixture.detectChanges() ,可能會覺得比較繁瑣,那 Angular 可不可以在測試環(huán)境中自動運(yùn)行變化檢測呢?

我們可以通過配置  ComponentFixtureAutoDetect  來實現(xiàn)

TestBed.configureTestingModule({
  declarations: [ BannerComponent ],
  providers: [
    { provide: ComponentFixtureAutoDetect, useValue: true }
  ]
});

然后再回頭看看剛才的示例:

Angular中的變化檢測實例分析

上面的示例我們并沒有調(diào)用 fixture.detectChanges() ,但是測試依然通過了,這是因為我們開啟了自動變化檢測。

再看一個示例:

Angular中的變化檢測實例分析

上面的示例中,我們在測試代碼中動態(tài)修改了 title 的值,測試運(yùn)行失敗,這是因為 Angular 并不知道測試改變了組件, ComponentFixtureAutoDetect  只對異步操作進(jìn)行自動變化檢測,例如 Promise、setTimeout、click 等DOM事件等,如果我們手動更改了綁定值,我們依然還需要調(diào)用 fixture.detectChanges()  來執(zhí)行變化檢測。

常見的坑

ngModel

Angular中的變化檢測實例分析

上面這個示例,綁定值修改后調(diào)用了 fixture.detectChanges() , 但是運(yùn)行測試后仍然報錯,這是為什么呢?

Angular中的變化檢測實例分析

查看Angular源碼后我們發(fā)現(xiàn) ** ngModel 的值是通過異步更新的** ,執(zhí)行fixture.detectChanges() 后雖然觸發(fā)了變化檢測,但是值還并未修改成功。

修改一下測試:

Angular中的變化檢測實例分析

修改后我們將斷言包裹在了 fixture.whenStable()  中,然后測試通過,那 whenStable()  是什么呢?

whenStable(): Promise : 當(dāng)夾具穩(wěn)定時解析的承諾 當(dāng)事件已觸發(fā)異步活動或異步變更檢測后,可用此方法繼續(xù)執(zhí)行測試。

當(dāng)然除了用 fixture.whenStable()  我們也可以用 tick()  來解決這個問題

Angular中的變化檢測實例分析

tick() :為 fakeAsync Zone 中的計時器模擬異步時間流逝 在此函數(shù)開始時以及執(zhí)行任何計時器回調(diào)之后,微任務(wù)隊列就會耗盡

測試 OnPush 組件

Angular中的變化檢測實例分析

上面這個示例,我們在修改屬性后調(diào)用了 fixture.detectChanges()  ,但是測試未通過,這是為什么呢?我們發(fā)現(xiàn)這個示例與第一個示例唯一的區(qū)別就是這個組件是一個 OnPush 組件,之前我們說過默認(rèn)變化檢測會跳過 OnPush 組件的,只有在特定的幾種情況下才會觸發(fā)變化檢測的,遇到這種情況如何解決呢?

Angular中的變化檢測實例分析

我們可以手動獲取組件的 ChangeDetectorRef  來主動觸發(fā)變化檢測。

延伸

虛擬DOM與增量DOM

Angular Ivy 是一個新的 Angular 渲染器,它與我們在主流框架中看到的任何東西都截然不同,因為它使用增量 DOM。 增量DOM是什么呢?它與虛擬Dom有什么不同呢?

虛擬 DOM

首先說一下虛擬DOM,我們要了解在瀏覽器中,直接操作Dom是十分損耗性能的,而虛擬DOM 的主要概念是將 UI的虛擬表示保存在內(nèi)存中,通過 Diff 操作對比當(dāng)前內(nèi)存和上次內(nèi)存中視圖的差異,從而減少不必要的Dom操作,只針對差異的Dom進(jìn)行更改。

虛擬DOM執(zhí)行流程:

  • 當(dāng) UI 發(fā)生變化時,將整個 UI 渲染到 Virtual DOM 中。

  • 計算先前和當(dāng)前虛擬 DOM 表示之間的差異。

  • 使用更改更新真實的 DOM。

Angular中的變化檢測實例分析

虛擬 DOM 的優(yōu)點:

  • 高效的 Diff 算法。

  • 簡單且有助于提高性能。

  • 沒有 React 也可以使用

  • 足夠輕量

  • 允許構(gòu)建應(yīng)用程序且不考慮狀態(tài)轉(zhuǎn)換

增量 DOM

增量Dom的主要概念是將組件編譯成一系列的指令,這些指令去創(chuàng)建DOM樹并在數(shù)據(jù)更改時就地的更新它們。

Angular中的變化檢測實例分析

例如:

@Component({
  selector: 'todos-cmp',
  template: `
    <p *ngFor="let t of todos|async">
        {{t.description}}
    </p>
  `
})
class TodosComponent {
  todos: Observable<Todo[]> = this.store.pipe(select('todos'));
  constructor(private store: Store<AppState>) {}
}

編譯后:

var TodosComponent = /** @class */ (function () {
  function TodosComponent(store) {
    this.store = store;
    this.todos = this.store.pipe(select('todos'));
  }

  TodosComponent.ngComponentDef = defineComponent({
    type: TodosComponent,
    selectors: [["todos-cmp"]],
    factory: function TodosComponent_Factory(t) {
      return new (t || TodosComponent)(directiveInject(Store));
    },
    consts: 2,
    vars: 3,
    template: function TodosComponent_Template(rf, ctx) {
      if (rf & 1) { // create dom
        pipe(1, "async");
        template(0, TodosComponent_p_Template_0, 2, 1, null, _c0);
      } if (rf & 2) { // update dom
        elementProperty(0, "ngForOf", bind(pipeBind1(1, 1, ctx.todos)));
      }
    },
    encapsulation: 2
  });

  return TodosComponent;
}());

增量DOM的優(yōu)點:

  • 渲染引擎可以被Tree Shakable,降低編譯后的體積

  • 占用較低的內(nèi)存

為什么可渲染引擎可以被 Tree Shakable?

Tree Shaking 是指在編譯目標(biāo)代碼時移除上下文中未引用的代碼 ,增量 DOM 充分利用了這一點,因為它使用了基于指令的方法。正如示例所示,增量 DOM 在編譯之前將每個組件編譯成一組指令,這有助于識別未使用的指令。在 Tree Shakable 過程中,可以將這些未使用的的指令刪除掉。

減少內(nèi)存的使用

與虛擬 DOM 不同,增量 DOM 在重新呈現(xiàn)應(yīng)用程序 UI 時不會生成真實 DOM 的副本。此外,如果應(yīng)用程序 UI 沒有變化,增量 DOM 就不會分配任何內(nèi)存。大多數(shù)情況下,我們都是在沒有任何重大修改的情況下重新呈現(xiàn)應(yīng)用程序 UI。因此,按照這種方法可以極大的減少設(shè)備內(nèi)存使用。

關(guān)于“Angular中的變化檢測實例分析”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI