angular2 髒檢查series1-Zone.js

angular2 髒檢查總述

這系列文章將介紹angular2的髒值檢查是如何工做的?如何比ng1更高效?帶着上述問題,讓咱們一塊兒來看看angular2這禽獸(誰讓它叫angular,又那麼生猛)幹了什麼。javascript

什麼是髒值檢查

片面的說髒檢查是對比當前的數據和曾經的數據是否發生改變。而在這個context下,我想介紹的是angular2從發現數據的變化到找到變化的點到更新DOM的整個過程。也就是說這裏所說的髒值檢查是Viewmodel與view層的那座橋樑。先看下面的圖,紅色表示改變的節點。php

clipboard.png

那麼問題來了,angular2是如何知道數據發生了改變?又是如何知道須要修改DOM的位置,準確的最小範圍的修改DOM呢?沒錯,儘量小的範圍修改DOM,由於操做DOM對於性能來講但是一件奢侈品。別急,讓咱們先看看沒有angular咱們如何實現數據改變到view的改變。在web古老的年代,那個asp.net、j2ee、php的時代,請求+整頁重繪,那時啪啪啪的重繪聲,現在依然迴盪在心中,痛苦不可磨滅。再來看看SPA時代,其它framework的解決方案,最值得一提的是名聲在外的react用了diff虛擬DOM的方式,也實現了最小化更新DOM。有興趣能夠看看Tero的這篇博客,比較了不少流行框架對這個問題的解決。
http://teropa.info/blog/2015/...
回到上面問題,angular2是如何知道數據發生了改變?細心的你可能會發現,在angular2 示例項目中都引入了一個Zone.js的東西。Zone.js是什麼鬼?html

angular2 髒檢查Series1之Zone.js

Zone能作什麼?

Zone提供方便的方式」進入」異步函數執行上下文(注意進入有引號,後面解釋),並能在異步執行環境中加入一些鉤子的東西。
爲何須要進入異步函數的執行上下文?這是我看到zone.js的github的第一個問題。咱們先來看看這樣一個場景。java

foo();
setTimeout(doSomething, 2000);
bar();
baz();

我任性的提出一個問題,我想知道上面doSomething函數在這個上下文中何時開始執行的?要知道爲了避免阻塞UI界面的用戶體驗,在JavaScript執行的不少耗時操做都被封裝爲了異步操做,如:setTimeout、XMLHttpRequest、DOM事件等。也就是說doSomething會進入事件循環。 這個時候是否是特別指望,能進入doSomething的執行環境,拿到點證據控告寫doSomething這個函數的程序員寫得垃圾?可能你已經想到了解決辦法,雖然doSomething的執行上下文我進不了。但我能夠wrap一下doSomething僞造一個執行上下文,在這個上下文中作點手腳,哼哼.. 恭喜你,你已經有了和Zone.js團隊成員同樣的思想覺悟。
這也是爲何上面提到的Zone提供方便的方式」進入」異步函數執行上下文中進入加了引號。並非真正的進入,而是經過包裹的方式僞造執行上下文,並經過鉤子函數方便的進入執行環境。這個場景看似有些極端,但在異步Task跟蹤,分析,錯誤記錄、開發調試跟蹤等場景,都有這樣的需求。下面咱們來看看Zone是如何提供方便的。react

Zone如何使用

demo1git

var profilingZone = (function () {
    var time = 0,
        timer = performance ?
                    performance.now.bind(performance) :
                    Date.now.bind(Date);
    return {
      beforeTask: function () {
        this.start = timer();
        console.log(‘beforeTask time:’+this.start);
      },
      afterTask: function () {
        time += timer() - this.start;
        console.log(‘afterTask time:’+time);
      }
    };
  }());

zone.fork(profilingZone).run(function(){
foo();
setTimeout(doSomething, 2000);
bar();
baz();
});

demo1運行結果程序員

// beforeTask time:3073872.9000000004
// AfterTask time:1.04500000039116
// beforeTask time:3075873.165
// AfterTask time:1.2550000004

能夠從上面的demo看到運用Zone提供的beforeTask,afterTask鉤子函數方便的進入了doSomething執行的上下文,記錄了時間。值得一提的是,咱們並無對doSomething作任何處理,咱們所作的只是在doSomething外部作了點改動。就達到了進入doSomething執行上下文的目的。彷佛看到了AOP的思想(說到AOP我又想到了ng2的annotation,找個時間好好分享一下)。 除此以外Zone還提供了一些其它鉤子函數。請參考:https://github.com/angular/zo...github

Zone原理

yo! check it out! demo的運行結果爲何會有輸出兩次beforeTask和AfterTask?要想解答這個問題,咱們先來看看Zone運行的原理。前面提到過Zone僞造一個執行上下文,實際上Zone有一個叫猴子補丁的東西。在Zone.js運行時,就會爲這些異步事件作一層代理包裹,也就是說Zone.js運行後,調用setTimeout、addEventListener等瀏覽器異步事件時,再也不是調用原生的方法,而是被猴子補丁包裝事後的代理方法。wo!猴子補丁真牛逼,它是怎麼把這些原生的事件都進行包裝改造後進化成「猴子」的呢?其實很簡單,其實並不難..只須要暴力點!再暴力點!web

//如下是Zone.js啓動時執行邏輯的抽象代碼片斷
function zoneAwareAddEventListener() {...}
function zoneAwareRemoveEventListener() {...}
function zoneAwarePromise() {...}
function patchTimeout() {...}
window.prototype.addEventListener = zoneAwareAddEventListener;
window.prototype.removeEventListener = zoneAwareRemoveEventListener;
window.prototype.promise = zoneAwarePromise;
window.prototype.setTimeout = patchTimeout;

確實很暴力,直接原生覆蓋了!原生的異步方法都被代理覆蓋了,代理裏setup了鉤子函數,這還不能徹底解決問題。咱們還有個需求,須要「因人而異」的處理這些暴露的鉤子函數。例如api

setTimeout(doA, 2000);
setTimeout(doB, 2000);

這裏有兩個方法doA和doB,總不能用鉤子函數裏只能作一樣的事情吧。因此會有一個根zone和fork。fork能夠擴展一個新的zone。而每一個zone都有本身的生命週期。爲了理解這個問題咱們再來看個Demo

demo2

//fork一個新的zone,咱們給它暫定個名字叫temporary zone.
Zone.current.fork({}).run(function () {
    //調用beforeTask等鉤子(zone內部處理)
    //run 內部Zone.current指向temporary zone(zone內部作的處理),並添加一個inTheZone屬性設置爲true.
    Zone.current.inTheZone = true;
    //調用被猴子補丁包裝後的setTimeout方法,並將包裝後的greet方法內部的zone設置成當前的temporary zone,並將函數greet加入事件循環.
    setTimeout(function greet() {
        console.log('in the zone: ' + !!Zone.current.inTheZone);
    }, 0);
    //要在zone run中執行的內容已經執行完了,調用AfterTask鉤子.(zone內部處理)
    //    //調用afterTask等鉤子.(zone內部處理)
    //zone.current引用替換成根zone,由於run外部的zone不該該是fork後的zone,fork後的zone生命週期隨着run的結束而結束.(zone內部處理)
});

console.log('in the zone: ' + !!Zone.current.inTheZone);

demo2輸出結果

in the zone: false
in the zone: true

但願更好的理解,我在demo中加了註釋以說明zone生命週期的問題.咱們能夠看到fork後的temporary zone生命週期隨着run執行的結束而結束.因此run外部的console.log取不到Zone.current裏的屬性inTheZone(temporary zone中的inTheZone)而在greet真正執行時,也會經歷和run內部同樣的過程(鉤子函數的執行,zone的引用替換銷燬等).而包裹後的greet內部的zone指向的是在setTimeout傳入greet上下文中的(當前做用域中)temporary zone.
如今再回頭看看demo1中爲何會輸出兩次beforeTask和AfterTask,也正是由於zone特定的生命週期所形成的.

Zone.js在angular2中的運用

還記得大明湖畔ng1的$scope.$apply嗎?任何原生的事件都不會觸發髒檢查,必須得調用$scope.$apply來告訴angular。個人數據有更新了,你同步更新下UI吧。而在angular2中有了Zone.js。原生隨便用,setTimeout,addEventListener、promise等都在ngZone中執行,angular並在ngZone中setup了相應的鉤子,通知angular2作相應的髒檢查處理,而後更新DOM。


如何髒檢查?如何更新DOM?比起angular1有什麼新的變化?下章再見。但願上述內容能給你一些幫助。若有任何疑問與不足,歡迎指出並討論。

相關文章
相關標籤/搜索