1. 程式人生 > >Angularjs與Angular對髒檢查機制的理解

Angularjs與Angular對髒檢查機制的理解

“髒檢查”是Angular中的核心機制之一,它是實現雙向資料繫結、MVVM模式的重要基礎。

AngularJS常用函式:$apply,$watch及$digest

$digest是一個內部函式,正常的應用程式碼中是不應該直接呼叫它的。要想主動觸發它,就要呼叫scope.$apply函式,它是觸發Angular“髒檢查機制”的常用公開介面。$digest迴圈實際上包括兩個while迴圈。分別是:處理$evalAsync的非同步運算佇列,處理$watch的watchers佇列。當該迴圈觸發時,它會遍歷當前$scope及其所有子$scope上已註冊的所有watchers函式。遍歷一遍所有watcher函式成為一輪髒檢查。執行完一輪髒檢查,如果任何一個watcher所監聽的值改變過,那麼就會重新再進行一輪髒檢查,直到所有的watcher函式都報告其所監聽的值不改變了。當迴圈結束時,才把模型的變化結果更新到DOM中去。這樣做為了,防止頻繁更新DOM屬性。

通俗理解髒檢查:表示式{{aaa.x}},AngularJS不僅會渲染該資料,還會為該特定值建立一個觀察程式。之後,只要程式發生任何事情,AngularJS就會檢查該觀察過程中的值是否更改。如果有,重新呈現表示式。執行這些觀察者的過程稱為髒檢查。

Angular處理髒檢查引用的是Zone.js

NgZone是一個帶有基於Observables的附加api的分叉區域

想要資料發生變化應用到頁面上,首先需要檢測資料的變化,資料變化一般發生非同步事件中,如:

瀏覽器事件,eg:click,submit

setTimeout和setInterval

XHR,從遠端伺服器獲取資料……

誰通知了Angular?

class ApplicationRef {

    changeDetectorRefs:ChangeDetectorRef[] = [];

    // applicationRef在構造器中監聽onTurnDone事件
    constructor(private zone: NgZone) {
        this.zone.onTurnDone
        .subscribe(() => this.zone.run(() => this.tick()))
    } 

    // tick函式遍歷所有的探測器的介面/物件  對其執行檢測
    tick() {
        this.changeDetectorRefs
        .forEach((ref) => ref.detectChanges());
        }
    }

}

變更檢測是如何進行的呢?

每一個元件都有屬於自己的變更檢測器(change detector)

變更檢測樹:有向圖  單向資料流   始終都是由上而下執行更改檢測

預設情況下,Angular是保守的,每次都會檢查每個元件

變更檢測策略

enum ChangeDetectionStrategy {

        OnPush: 0 // 僅在輸入已更改時才對檢視進行更改檢測。當輸入屬性不變時,Angular可以跳過整個變更檢測樹

         Default: 1  // 預設策略,其中更改檢測是自動的,直到明確停用

}

NgDoCheck鉤子和變更檢測

更新子元件的屬性

呼叫位於子元件中的NgDoCheck生命週期鉤子

更新當前元件的DOM

向子元件執行變更檢測

NgDoCheck作用:配合markForCheck和OnPush

export class AppComponent {
    @Input() data;
    
    
    public id;

    constructor(private cdr: ChangeDetectorRef){}

    ngOnChanges() {
        // 當data改變時,更新id
        this.id = this.data.id;
    }
    

    ngDoCheck() {
        // 在ngDoCheck中檢測data這個object的屬性是否更改
        if( this.id !== this.data.id ) {
            this.cdr.markForCheck();
        }
    }
    
}
@Component({
    template:'{{num}}'
    changeDetection: ChangeDetectionStrategy.OnPush
})

export class NumComponent implements OnInit {
    @Input() addItem: Observable<any>;
    
    num = 0;

    constructor(private cdr: ChangeDetectorRef){}
    ngOnInit() {
        this.addItem.subscribe(()=>{
            this.num++;
            this.cdr.markForCheck();//人為通知Angular進行檢測
        })
    }
}

Question?

為什麼在OnPush策略下,即使元件沒有屬性更新,ngOnCheck鉤子仍然被呼叫?

存在一種可能是子元件使用了OnPush策略而父元件沒有,子元件的@Input屬性沒有更改,但是ngOnCheck鉤子執行的是當前元件變化的呼叫。