這系列文章將介紹angular2的髒值檢查是如何工做的?如何比ng1更高效?帶着上述問題,讓咱們一塊兒來看看angular2這禽獸(誰讓它叫angular,又那麼生猛)幹了什麼。javascript
片面的說髒檢查是對比當前的數據和曾經的數據是否發生改變。而在這個context下,我想介紹的是angular2從發現數據的變化到找到變化的點到更新DOM的整個過程。也就是說這裏所說的髒值檢查是Viewmodel與view層的那座橋樑。先看下面的圖,紅色表示改變的節點。php
那麼問題來了,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
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
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
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特定的生命週期所形成的.
還記得大明湖畔ng1的$scope.$apply嗎?任何原生的事件都不會觸發髒檢查,必須得調用$scope.$apply來告訴angular。個人數據有更新了,你同步更新下UI吧。而在angular2中有了Zone.js。原生隨便用,setTimeout,addEventListener、promise等都在ngZone中執行,angular並在ngZone中setup了相應的鉤子,通知angular2作相應的髒檢查處理,而後更新DOM。
如何髒檢查?如何更新DOM?比起angular1有什麼新的變化?下章再見。但願上述內容能給你一些幫助。若有任何疑問與不足,歡迎指出並討論。