成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

Angular中的變化檢測(cè)實(shí)例分析

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

成都創(chuàng)新互聯(lián)公司擁有十多年成都網(wǎng)站建設(shè)工作經(jīng)驗(yàn),為各大企業(yè)提供網(wǎng)站設(shè)計(jì)制作、網(wǎng)站設(shè)計(jì)服務(wù),對(duì)于網(wǎng)頁(yè)設(shè)計(jì)、PC網(wǎng)站建設(shè)(電腦版網(wǎng)站建設(shè))、app軟件開(kāi)發(fā)、wap網(wǎng)站建設(shè)(手機(jī)版網(wǎng)站建設(shè))、程序開(kāi)發(fā)、網(wǎng)站優(yōu)化(SEO優(yōu)化)、微網(wǎng)站、主機(jī)域名等,憑借多年來(lái)在互聯(lián)網(wǎng)的打拼,我們?cè)诨ヂ?lián)網(wǎng)網(wǎng)站建設(shè)行業(yè)積累了很多網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、網(wǎng)絡(luò)營(yíng)銷經(jīng)驗(yàn),集策劃、開(kāi)發(fā)、設(shè)計(jì)、營(yíng)銷、管理等網(wǎng)站化運(yùn)作于一體,具備承接各種規(guī)模類型的網(wǎng)站建設(shè)項(xiàng)目的能力。

Angular中的變化檢測(cè)實(shí)例分析

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

什么是變化檢測(cè) ?

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

Angular中的變化檢測(cè)實(shí)例分析

變化檢測(cè)觸發(fā)時(shí)機(jī)

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

Demo1:

一個(gè)計(jì)數(shù)器組件,點(diǎn)擊按鈕Count會(huì)一直加 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:

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

  @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;
      });
    }
  }

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

  • 點(diǎn)擊事件發(fā)生時(shí)

  • 通過(guò) http 請(qǐng)求遠(yuǎn)程數(shù)據(jù)時(shí)

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

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

@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;
  }
}

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

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

  • setInterval()  和  setTimeout()

  • HTTP 通過(guò)  XMLHttpRequest  進(jìn)行請(qǐng)求

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

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

Zone.js

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

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

下面用一段簡(jiǎn)化的代碼來(lái)模擬一下替換 setTimeout 的過(guò)程:

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

NgZone

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

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

變化檢測(cè)是如何工作的?

我們了解 Angular 的核心是 組件化,組件的嵌套會(huì)使得最終形成一棵 組件樹(shù)

Angular中的變化檢測(cè)實(shí)例分析

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

Angular中的變化檢測(cè)實(shí)例分析

在組件中我們可以通過(guò)注入 ChangeDetectorRef  來(lái)獲取組件的 changeDetector

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

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

下是簡(jiǎn)化的代碼:

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

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

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

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

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

我們看一個(gè)示例:

@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中的變化檢測(cè)實(shí)例分析

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

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

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

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

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

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

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

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

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

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

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

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

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

Angular中的變化檢測(cè)實(shí)例分析

變化檢測(cè)的性能

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

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

變化檢測(cè)的策略

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

  • Default

  • OnPush

Default 策略

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

Angular中的變化檢測(cè)實(shí)例分析

OnPush 策略

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

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

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

Angular中的變化檢測(cè)實(shí)例分析

OnPush模式下變化檢測(cè)流程

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

  • 輸入值(@Input)更改

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

  • 手動(dòng)觸發(fā)變化檢測(cè)

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

輸入值(@Input)更改

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

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

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ā)變化檢測(cè)(針對(duì)組件樹(shù)中的所有組件)。

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

  • setTimeout()

  • setInterval()

  • Promise.resolve().then()

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

手動(dòng)觸發(fā)變化檢測(cè)

有三種手動(dòng)觸發(fā)更改檢測(cè)的方法:

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

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

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

Angular中的變化檢測(cè)實(shí)例分析

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

使用 async 管道

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

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

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

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

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

場(chǎng)景1:

假如我們?cè)趯?shí)現(xiàn)一個(gè)回車搜索的功能:

@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)什么問(wèn)題呢?

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

在這種情況下,我們就可以利用 NgZone.runOutsideAngular()  來(lái)減少變化檢測(cè)的次數(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);
                    });
                }
            });
        });
    }
}

場(chǎng)景2:

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

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

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

脫離 Zone.js 開(kāi)發(fā)

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

如何移除 Zone.js?

Angular中的變化檢測(cè)實(shí)例分析

手動(dòng)調(diào)用變化檢測(cè)

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

Angular中的變化檢測(cè)實(shí)例分析

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

?detectChanges:因?yàn)槟承┬史矫娴脑?,?nèi)部文檔不推薦使用  ?detectChanges  而推薦使用  ?markDirty , ?detectChanges 會(huì)觸發(fā)組件以子組件的變更檢測(cè)。

移除后的性能

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

移除前:

Angular中的變化檢測(cè)實(shí)例分析

移除后:

Angular中的變化檢測(cè)實(shí)例分析

測(cè)試與變化檢測(cè)

組件綁定

我們先來(lái)看一個(gè)組件綁定的例子:

Angular中的變化檢測(cè)實(shí)例分析

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

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

修改一下上面的Case:

origin-url0.00KB

origin-url0.00KB

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

如果我們?cè)跍y(cè)試中動(dòng)態(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');
});

自動(dòng)變更檢測(cè)

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

我們可以通過(guò)配置  ComponentFixtureAutoDetect  來(lái)實(shí)現(xiàn)

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

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

Angular中的變化檢測(cè)實(shí)例分析

上面的示例我們并沒(méi)有調(diào)用 fixture.detectChanges() ,但是測(cè)試依然通過(guò)了,這是因?yàn)槲覀冮_(kāi)啟了自動(dòng)變化檢測(cè)。

再看一個(gè)示例:

Angular中的變化檢測(cè)實(shí)例分析

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

常見(jiàn)的坑

ngModel

Angular中的變化檢測(cè)實(shí)例分析

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

Angular中的變化檢測(cè)實(shí)例分析

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

修改一下測(cè)試:

Angular中的變化檢測(cè)實(shí)例分析

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

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

當(dāng)然除了用 fixture.whenStable()  我們也可以用 tick()  來(lái)解決這個(gè)問(wèn)題

Angular中的變化檢測(cè)實(shí)例分析

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

測(cè)試 OnPush 組件

Angular中的變化檢測(cè)實(shí)例分析

上面這個(gè)示例,我們?cè)谛薷膶傩院笳{(diào)用了 fixture.detectChanges()  ,但是測(cè)試未通過(guò),這是為什么呢?我們發(fā)現(xiàn)這個(gè)示例與第一個(gè)示例唯一的區(qū)別就是這個(gè)組件是一個(gè) OnPush 組件,之前我們說(shuō)過(guò)默認(rèn)變化檢測(cè)會(huì)跳過(guò) OnPush 組件的,只有在特定的幾種情況下才會(huì)觸發(fā)變化檢測(cè)的,遇到這種情況如何解決呢?

Angular中的變化檢測(cè)實(shí)例分析

我們可以手動(dòng)獲取組件的 ChangeDetectorRef  來(lái)主動(dòng)觸發(fā)變化檢測(cè)。

延伸

虛擬DOM與增量DOM

Angular Ivy 是一個(gè)新的 Angular 渲染器,它與我們?cè)谥髁骺蚣苤锌吹降娜魏螙|西都截然不同,因?yàn)樗褂迷隽?DOM。 增量DOM是什么呢?它與虛擬Dom有什么不同呢?

虛擬 DOM

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

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

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

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

  • 使用更改更新真實(shí)的 DOM。

Angular中的變化檢測(cè)實(shí)例分析

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

  • 高效的 Diff 算法。

  • 簡(jiǎn)單且有助于提高性能。

  • 沒(méi)有 React 也可以使用

  • 足夠輕量

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

增量 DOM

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

Angular中的變化檢測(cè)實(shí)例分析

例如:

@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)點(diǎn):

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

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

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

Tree Shaking 是指在編譯目標(biāo)代碼時(shí)移除上下文中未引用的代碼,增量 DOM 充分利用了這一點(diǎn),因?yàn)樗褂昧嘶谥噶畹姆椒āU缡纠?,增?DOM 在編譯之前將每個(gè)組件編譯成一組指令,這有助于識(shí)別未使用的指令。在 Tree Shakable 過(guò)程中,可以將這些未使用的的指令刪除掉。

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

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

關(guān)于“Angular中的變化檢測(cè)實(shí)例分析”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。

文章題目:Angular中的變化檢測(cè)實(shí)例分析
網(wǎng)頁(yè)URL:http://jinyejixie.com/article28/gcspjp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供、網(wǎng)站收錄、網(wǎng)頁(yè)設(shè)計(jì)公司定制開(kāi)發(fā)、關(guān)鍵詞優(yōu)化、移動(dòng)網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

逊克县| 两当县| 虞城县| 宁津县| 平塘县| 景宁| 依兰县| 磐安县| 彰武县| 安新县| 安新县| 石泉县| 泰和县| 南城县| 肇东市| 韶关市| 开鲁县| 阆中市| 明溪县| 巫溪县| 凤台县| 蒙自县| 富裕县| 汉阴县| 扎兰屯市| 兰州市| 德化县| 洪江市| 尖扎县| 弥渡县| 二手房| 东海县| 秭归县| 溆浦县| 林芝县| 酒泉市| 东乌珠穆沁旗| 宾阳县| 漯河市| 五寨县| 镇安县|