iT邦幫忙

DAY 29
0

我在前端 ng 時系列 第 29

生生不息的 watcher - 介紹 data-binding

上一篇 bindonce 中介紹 AngularJS 的效能不好是因為實作 $digest cycle 的關係。

這篇就來深入了解到底 $digest cycle 怎麼作用的。


先把 AngularJS application 當作一個大工廠,
然後我們每次做 data-binding 就代表把一個工人 ($watch) 放進工廠裡。
每一位工人有自己的需要做的零件。(model)

有一次,工人小明 ($watch) 要更新零件的外觀,所以他跑去跟工頭 (scope) 說 “我的零件要更新了”。($apply)
工頭接到零件要改的需求,就跑去跟最上頭的老闆說 ($rootScope)
我們組的小明零件要更新,請檢查工廠內其他零件會不會被影響”。

這時候,老闆就會通知全部的工頭 ( all children scope) 說 “要檢查每個零件了
( $rootScope.$digest(), enter $digest cycle)。
依序檢察每個工人負責的零件 (loop $$watcher list)。
直到確定每個零件都是最新裝態,才會結束 digest cycle。

當工人 ($watcher) 越多,我們花費再檢查的零件時間就會越久。
這就是 AngularJS 為了保持資料的完整所付出的效能代價。

上面故事在套用 AngularJS 官網解釋 digest cycle 的圖。

(圖片來源:Integration with the browser event loop

再來幾個小故事:

當工廠因小明的零件在做更新檢查的時候,發現工人小華的零件是需要被更新的。
這時候小華的工頭 (scope) 就會跑去跟老闆說,又有零件要更新了。
老闆就會等這次小明檢查後,再跑一次因小華更新所做的檢查。(other digest cycle)。
直到確保全部的工人都說他們的零件沒問題才會結束。

可是老闆總不能讓檢查一直更新,因為裡頭有可能有 cycle。
ex: 小明的更新影響小華,小華的更新又會影響小明。
這時候 AngularJS 就開啟了 TTL 檢查(times to loop, 預設 10 次)。
當 digest cycle 跑超過 10 次的時候,不管怎樣,就會停止任何的 digest cycle。

當小明零件在檢查的時候,工頭小文突然跑去跟老闆 ($rootScopt) 說 “工廠可以同時進另外的檢查嗎?”。
老闆這時候就會丟錯誤訊息給小文說,
”不行,我們現在已經在 $digest cycle 了,不能再有另外一個 $digest cycle 同時進行。"

Error: $digest already in progress

這是 AngularJS 所做的機制,要確保在檢查的過程中,資料不會被外部不確定因素干擾。
假如同時有兩個以上的 $digest cycle 同時進行,老闆會產生混亂,就不能確定資料的完整性了。

JavaScript 是 single-threaded event-driven language, 所以當進入 $digest cycle 時。
畫面會凍結直到檢查結束。

假如在每次檢查的時候,工人小倫都需要邊唱歌邊跳舞邊檢查零件。
導致每次到小輪的時候,檢查時間都花特別久。工廠停擺的時間就會被拉長。
這是我們不希望遇見的情況,所以請確保每次檢查的邏輯是簡潔的。

上面是我把大致會發生的事情寫下來,當然中間還有一些事情在進行。
ex: $evalAsync queue, error handling,等。

有興趣可以看看下列文章 :-)。

參考文件:
What you need to know about AngularJS data binding by vkarpov15
Speeding up AngularJS apps with simple optimizations by Todd Motto
Make Your Own AngularJS, part 1: Scopes And Digest by Tero Parviainen


題外話:檢查畫面上有多少 watcher

把下面的程式碼貼在 consoler,就會跟你說目前畫面上有多少 watch
個人覺得不要超過 1000 個 watcher, 會影響效能。

(function () { 
    var root = $(document.getElementsByTagName('body'));
    var watchers = [];

    var f = function (element) {
        if (element.data().hasOwnProperty('$scope')) {
            angular.forEach(element.data().$scope.$$watchers, function (watcher) {
                watchers.push(watcher);
            });
        }

        angular.forEach(element.children(), function (childElement) {
            f($(childElement));
        });
    };

    f(root);

    console.log(watchers.length);
})()

from Top 10 mistakes AngularJS developers make by Mark Meyer
跟 StackOverFlow: How to count total number of watches on a pages


上一篇
可以再快一點嗎? - 介紹 bindonce
下一篇
AngularJS 1.3 跟 一點點的 2.0 (外加鐵人心得)
系列文
我在前端 ng 時30

尚未有邦友留言

立即登入留言