摘要:并不是真正的進入,而是通過包裹的方式偽造執行上下文,并通過鉤子函數方便的進入執行環境。如何使用運行結果可以從上面的看到運用提供的,鉤子函數方便的進入了執行的上下文,記錄了時間。我們還有個需求,需要因人而異的處理這些暴露的鉤子函數。
angular2 臟檢查總述
這系列文章將介紹angular2的臟值檢查是如何工作的?如何比ng1更高效?帶著上述問題,讓我們一起來看看angular2這禽獸(誰讓它叫angular,又那么生猛)干了什么。
什么是臟值檢查片面的說臟檢查是對比當前的數據和曾經的數據是否發生改變。而在這個context下,我想介紹的是angular2從發現數據的變化到找到變化的點到更新DOM的整個過程。也就是說這里所說的臟值檢查是Viewmodel與view層的那座橋梁。先看下面的圖,紅色表示改變的節點。
那么問題來了,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是什么鬼?
Zone提供方便的方式”進入”異步函數執行上下文(注意進入有引號,后面解釋),并能在異步執行環境中加入一些鉤子的東西。
為什么需要進入異步函數的執行上下文?這是我看到zone.js的github的第一個問題。我們先來看看這樣一個場景。
foo(); setTimeout(doSomething, 2000); bar(); baz();
我任性的提出一個問題,我想知道上面doSomething函數在這個上下文中什么時候開始執行的?要知道為了不阻塞UI界面的用戶體驗,在JavaScript執行的很多耗時操作都被封裝為了異步操作,如:setTimeout、XMLHttpRequest、DOM事件等。也就是說doSomething會進入事件循環。 這個時候是不是特別期望,能進入doSomething的執行環境,拿到點證據控告寫doSomething這個函數的程序員寫得垃圾?可能你已經想到了解決辦法,雖然doSomething的執行上下文我進不了。但我可以wrap一下doSomething偽造一個執行上下文,在這個上下文中做點手腳,哼哼.. 恭喜你,你已經有了和Zone.js團隊成員一樣的思想覺悟。
這也是為什么上面提到的Zone提供方便的方式”進入”異步函數執行上下文中進入加了引號。并不是真正的進入,而是通過包裹的方式偽造執行上下文,并通過鉤子函數方便的進入執行環境。這個場景看似有些極端,但在異步Task跟蹤,分析,錯誤記錄、開發調試跟蹤等場景,都有這樣的需求。下面我們來看看Zone是如何提供方便的。
demo1
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...
Zone原理yo! check it out! demo的運行結果為什么會有輸出兩次beforeTask和AfterTask?要想解答這個問題,我們先來看看Zone運行的原理。前面提到過Zone偽造一個執行上下文,實際上Zone有一個叫猴子補丁的東西。在Zone.js運行時,就會為這些異步事件做一層代理包裹,也就是說Zone.js運行后,調用setTimeout、addEventListener等瀏覽器異步事件時,不再是調用原生的方法,而是被猴子補丁包裝過后的代理方法。wo!猴子補丁真牛逼,它是怎么把這些原生的事件都進行包裝改造后進化成“猴子”的呢?其實很簡單,其實并不難..只需要暴力點!再暴力點!
//以下是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了鉤子函數,這還不能完全解決問題。我們還有個需求,需要“因人而異”的處理這些暴露的鉤子函數。例如
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有什么新的變化?下章再見。希望上述內容能給你一些幫助。如有任何疑問與不足,歡迎指出并討論。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80322.html
摘要:策略減少檢測次數當輸入屬性不變時,可以跳過整個變更檢測子樹。現在當執行更改檢測時,它將從上到下進行。并且一旦更改檢測運行結束,它將恢復整個樹的狀態。 Angular 2.x+ 臟檢查機制理解 目前幾種主流的前端框架都已經實現雙向綁定特性,但實現的方法各有不同: 發布者-訂閱者模式(backbone.js) 臟值檢查(angular.js) 數據劫持 + 發布者-訂閱者模式(vue.j...
摘要:我們再來看一下調用棧,如下圖從圖中我們發現了一個調用棧的代碼執行過,還記得里提到嗎發起臟檢查的通知者,它代理了原生事件,任何一個原生異步事件的觸發都會導致的運行。 尋找真兇Echarts or Angular 這是一篇故事,就如同技術,我們所追求的不是一個結局,而是那些深受啟發與共鳴的過程,那是我們成長的經驗與生產力的積淀! 故事開始于瘋了的ionic3應用 頁面打開,什么也沒做5s里...
angular2是什么?我猜不容我贅述,各位一定略有耳聞,無論是曾經AngularJS的擁躉,亦或是React的粉絲,都或多或少的對她有過一點了解。未見其物、先聞其聲,angular2在問世之前已經做足了宣傳,想必諸位也一定被下面各種詞匯所震懾,什么:TypeScript、 ES5、 ES6、 Dart、 Immutable、 Unidirectional Data Flow、 Reactive ...
閱讀 1225·2021-11-11 16:54
閱讀 1738·2021-10-13 09:40
閱讀 933·2021-10-08 10:05
閱讀 3498·2021-09-22 15:50
閱讀 3701·2021-09-22 15:41
閱讀 1782·2021-09-22 15:08
閱讀 2338·2021-09-07 10:24
閱讀 3571·2019-08-30 12:52