1. 程式人生 > >angular 2+ 變化檢測系列一

angular 2+ 變化檢測系列一

神奇 有趣 checked 重要 ner eve img 攔截 允許

什麽是變化檢測?

變化檢測的基本功能就是獲取應用程序的內部狀態(state),並且是將這種狀態對用戶界面保持可見.狀態可以是javascript中的任何的數據結構,比如對象,數組,(數字,布爾,字符串等基礎數據類型).這種狀態最終可能成為用戶界面中的段落,表單,鏈接或按鈕,在web瀏覽器中我們們稱之為文檔對象模型(dom).將數據結構作為輸入生成dom作為輸出並展示給用戶,我們稱這個過程為渲染.

技術分享圖片

但是,在運行時發生更改時會變得更加棘手。一段時間後,DOM已經被渲染。我們如何弄清楚模型中發生了哪些變化,以及我們需要在哪裏更新DOM?訪問DOM樹總是很昂貴,因此我們不僅需要找出需要更新的位置,而且還希望盡可能小地保持訪問權限.這可以通過許多不同的方式解決。例如,一種方法是簡單地發出http請求並重新呈現整個頁面。另一種方法是將新狀態的DOM與先前狀態進行區分並僅渲染差異的概念,這就是React使用Virtual DOM進行的操作.

因此,變更檢測的目標始終是預測數據及其變化。

什麽導致變化發生?

現在我們知道變化檢測的全部內容,我們可能想知道,何時會發生這樣的變化? Angular什麽時候知道它必須更新視圖?好吧,我們來看看下面的代碼

@Component({
  template: `
    <h1>{{firstname}} {{lastname}}</h1>
    <button (click)="changeName()">Change name</button>
  `
})
class MyApp {

  firstname:string = ‘Pascal‘;
  lastname:string = ‘Precht‘;

  changeName() {
    this.firstname = ‘Brad‘;
    this.lastname = ‘Green‘;
  }
}

上面的組件只顯示兩個屬性,並提供了一種方法,可以在單擊模板中的按鈕時更改它們。單擊此特定按鈕的那一刻是應用程序狀態發生更改的時刻,因為它會更改組件的屬性。那是我們想要更新視圖的那一刻。

又比如:

@Component()
class ContactsApp implements OnInit{

  contacts:Contact[] = [];

  constructor(private http: Http) {}

  ngOnInit() {
    this.http.get(‘/contacts‘)
      .map(res => res.json())
      .subscribe(contacts => this.contacts = contacts);
  }
}

該組件包含聯系人列表,在初始化時,它會執行http請求。一旦此請求返回,列表就會更新。同樣,此時,我們的應用程序狀態已更改,因此我們將要更新視圖。

應用程序狀態改變一般由以下3個方面引起:

  1. 事件: click,input,submit...
  2. XHR: 從遠程服務器獲取數據
  3. 定時器:setTimeout(),setInterval()

事實證明,這三件事有一些共同之處。你能說出來嗎? ......正確!它們都是異步的.

為什麽你認為這很重要?嗯......因為事實證明,當Angular真正對更新視圖感興趣時,這些是唯一的情況。假設我們有一個Angular組件,當單擊一個按鈕時它會執行一個處理程序:

@Component({
  selector: ‘my-component‘,
  template: `
    <h3>We love {{name}}</h3>
    <button (click)="changeName()">Change name</button>
  `
})
class MyComponent {

  name:string = ‘thoughtram‘;

  changeName() {
    this.name = ‘Angular‘;
  }
}

單擊組件的按鈕時,將執行changeName(),這將更改組件的name屬性,由於我們希望此更改也反映在DOM中,因此Angular將相應地更新視圖綁定{{name}}。很好,這似乎神奇地工作.

另一個例子是使用setTimeout()更新name屬性。請註意,我們刪除了該按鈕。我們不是必須去做一些特殊的事情來通知框架狀態 發生了變化

@Component({
  selector: ‘my-component‘,
  template: `
    <h3>We love {{name}}</h3>
  `
})
class MyComponent implements OnInit {

  name:string = ‘thoughtram‘;

  ngOnInit() {
    setTimeout(() => {
      this.name = ‘Angular‘;
    }, 1000);
  }
}

誰通知Angular進行變化檢測?

  Angular允許我們直接使用本機API。我們不需要調用攔截器方法,因此Angular會通知更新DOM。這是純粹的魔法嗎? 背後的秘密就是Angular利用了Zones庫.Zones猴子補丁全局異步操作,如setTimeout()和addEventListener(),這就是Angular可以輕松找到的原因,何時更新DOM. 簡短的版本是,在Angular的源代碼中,有一個名為ApplicationRef的東西,它監聽NgZones onTurnDone事件。每當觸發此事件時,它都會執行tick()函數,該函數基本上執行更改檢測。

// very simplified version of actual source
class ApplicationRef {

  changeDetectorRefs:ChangeDetectorRef[] = [];

  constructor(private zone: NgZone) {
    this.zone.onTurnDone
      .subscribe(() => this.zone.run(() => this.tick());
  }

  tick() {
    this.changeDetectorRefs
      .forEach((ref) => ref.detectChanges());
  }
}

變化檢測執行機制

一個重要的事實是:我們可以為每個組件單獨控制如何以及何時執行更改檢測

技術分享圖片

技術分享圖片

  由於每個組件都有自己的更改檢測器,而Angular應用程序由組件樹組成,因此邏輯結果是我們也有一個更改檢測器樹。此樹也可以視為有向圖,其中數據始終從頂部流向底部.數據從上到下流動的原因是因為每個單獨的組件,從根組件開始,每個組件也始終從上到下執行更改檢測。這很棒,因為單向數據流比循環更容易預測。相比之下,AngularJS采用的是雙向數據流,錯綜復雜的數據流使得它不得不多次檢查,使得數據最終趨向穩定。理論上,數據可能永遠不穩定。AngularJS給出的策略是,臟檢查超過10次,就認為程序有問題,不再進行檢查。我們總是知道我們在視圖中使用的數據來自何處,因為它只能來自其組件。在Angular 2+中,另一個有趣的觀察是一次通過後變化檢測變得穩定。這意味著,如果我們的某個組件在更改檢測期間第一次運行後導致任何其他副作用,Angular將拋出錯誤。在開發模式下,Angular會進行二次檢查,如果出現上述情況,二次檢查就會報錯:Expression Changed After It Has Been Checked Error。而在生產環境中,臟檢查只會執行一次。

技術分享圖片

    

變化檢測性能

  默認情況下,即使我們每次都要檢查事件發生時每個組件,Angular都非常快。它可以在幾毫秒內執行數十萬次檢查。這主要是因為Angular生成了VM友好代碼。那是什麽意思?好吧,當我們說每個組件都有自己的變化檢測器時,它不像Angular中的這個通用的東西,它負責每個組件的變化檢測。原因是它必須以動態方式編寫,因此無論模型結構如何,它都可以檢查每個組件。虛擬機不喜歡這種動態代碼,因為它們無法對其進行優化。它被認為是多態的,因為物體的形狀並不總是相同的。

  Angular在運行時為每個組件創建變化檢測器類,這些組件是單態的,因為它們確切地知道組件模型的形狀。 VM可以完美地優化此代碼,從而使其執行起來非常快。好消息是我們不必過多關心它,因為Angular會自動完成它。    

  本篇簡單介紹下angular 2+變化檢測的基礎,下一篇重點講一下變化檢測策略.

       

angular 2+ 變化檢測系列一