【轉】理解Angular中的$apply()以及$digest()

$apply()和$digest()在AngularJS中是兩個核心概念如庭,但是有時候它們又讓人困惑撼港。而為了了解angularjs的工作方式,首先需要了解$apply()和$digest()是如何工作的。這篇文章旨在解釋$apply()和$digest()是什么否灾,以及在日常的編碼中如何應用它們鸣奔。

探索$apply()和$digest()

AngularJS提供了一個非常酷的特性叫做雙向數(shù)據(jù)綁定(Two-way Data Binding)扣汪,這個特性大大簡化了我們的代碼編寫方式崭别。數(shù)據(jù)綁定意味著當View中有任何數(shù)據(jù)發(fā)生了變化恐锣,那么這個變化也會自動地反饋到scope的數(shù)據(jù)上,也即意味著scope模型會自動地更新诀姚。類似地赫段,當scope模型發(fā)生變化時,view中的數(shù)據(jù)也會更新到最新的值糯笙。那么AngularJS是如何做到這一點的呢?當你寫下表達式如{{ aModel }}時瘫寝,AngularJS在幕后會為你在scope模型上設置一個watcher焕阿,它用來在數(shù)據(jù)發(fā)生變化的時候更新view首启。這里的watcher和你會在AngularJS中設置的watcher是一樣的:

$scope.$watch('aModel', function(newValue, oldValue) {  
  //update the DOM with newValue  
});  

傳入到$watch()中的第二個參數(shù)是一個回調函數(shù)毅桃,該函數(shù)在aModel的值發(fā)生變化的時候會被調用。當aModel發(fā)生變化的時候莺掠,這個回調函數(shù)會被調用來更新view這一點不難理解读宙,但是结闸,還存在一個很重要的問題!AngularJS是如何知道什么時候要調用這個回調函數(shù)呢扎附?換句話說结耀,AngularJS是如何知曉aModel發(fā)生了變化,才調用了對應的回調函數(shù)呢香伴?它會周期性的運行一個函數(shù)來檢查scope模型中的數(shù)據(jù)是否發(fā)生了變化嗎即纲?好吧博肋,這就是$digest循環(huán)的用武之地了蜂厅。

在$digest循環(huán)中掘猿,watchers會被觸發(fā)唇跨。當一個watcher被觸發(fā)時买猖,AngularJS會檢測scope模型,如何它發(fā)生了變化那么關聯(lián)到該watcher的回調函數(shù)就會被調用飞主。那么高诺,下一個問題就是$digest循環(huán)是在什么時候以各種方式開始的虱而?

在調用了$scope.$digest()后,$digest循環(huán)就開始了胖烛。假設你在一個ng-click指令對應的handler函數(shù)中更改了scope中的一條數(shù)據(jù)诅迷,此時AngularJS會自動地通過調用$digest()來觸發(fā)一輪$digest循環(huán)罢杉。當$digest循環(huán)開始后滩租,它會觸發(fā)每個watcher利朵。這些watchers會檢查scope中的當前model值是否和上一次計算得到的model值不同。如果不同技即,那么對應的回調函數(shù)會被執(zhí)行而叼。調用該函數(shù)的結果,就是view中的表達式內容(譯注:諸如{{ aModel }})會被更新葵陵。除了ng-click指令,還有一些其它的built-in指令以及服務來讓你更改models(比如ng-model娇钱,$timeout等)和自動觸發(fā)一次$digest循環(huán)文搂。

目前為止還不錯考抄!但是,有一個小問題疯兼。在上面的例子中贫途,AngularJS并不直接調用$digest()丢早,而是調用$scope.$apply(),后者會調用$rootScope.$digest()傀缩。因此农猬,一輪$digest循環(huán)在$rootScope開始,隨后會訪問到所有的children scope中的watchers慷垮。

現(xiàn)在料身,假設你將ng-click指令關聯(lián)到了一個button上衩茸,并傳入了一個function名到ng-click上。當該button被點擊時祟牲,AngularJS會將此function包裝到一個wrapping function中说贝,然后傳入到$scope.$apply()。因此言询,你的function會正常被執(zhí)行傲宜,修改models(如果需要的話),此時一輪$digest循環(huán)也會被觸發(fā)辆憔,用來確保view也會被更新报嵌。

Note: $scope.$apply()會自動地調用$rootScope.$digest()。$apply()方法有兩種形式腕巡。第一種會接受一個function作為參數(shù)绘沉,執(zhí)行該function并且觸發(fā)一輪$digest循環(huán)豺总。第二種會不接受任何參數(shù),只是觸發(fā)一輪$digest循環(huán)另玖。我們馬上會看到為什么第一種形式更好沸枯。

什么時候手動調用$apply()方法绑榴?

如果AngularJS總是將我們的代碼wrap到一個function中并傳入$apply()翔怎,以此來開始一輪$digest循環(huán),那么什么時候才需要我們手動地調用$apply()方法呢赤套?實際上容握,AngularJS對此有著非常明確的要求,就是它只負責對發(fā)生于AngularJS上下文環(huán)境中的變更會做出自動地響應(即塑猖,在$apply()方法中發(fā)生的對于models的更改)谈跛。AngularJS的built-in指令就是這樣做的,所以任何的model變更都會被反映到view中蜡励。但是阻桅,如果你在AngularJS上下文之外的任何地方修改了model,那么你就需要通過手動調用$apply()來通知AngularJS占遥。這就像告訴AngularJS输瓜,你修改了一些models尤揣,希望AngularJS幫你觸發(fā)watchers來做出正確的響應。

比如北戏,如果你使用了JavaScript中的setTimeout()來更新一個scope model,那么AngularJS就沒有辦法知道你更改了什么旧蛾。這種情況下锨天,調用$apply()就是你的責任了剃毒,通過調用它來觸發(fā)一輪$digest循環(huán)搂赋。類似地脑奠,如果你有一個指令用來設置一個DOM事件listener并且在該listener中修改了一些models幅慌,那么你也需要通過手動調用$apply()來確保變更會被正確的反映到view中欠痴。

讓我們來看一個例子。加入你有一個頁面喇辽,一旦該頁面加載完畢了菩咨,你希望在兩秒鐘之后顯示一條信息。你的實現(xiàn)可能是下面這個樣子的:

<body ng-app="myApp">  
  <div ng-controller="MessageController">  
    Delayed Message: {{message}}  
  </div>    
</body>  
/* What happens without an $apply() */  
      
    angular.module('myApp',[]).controller('MessageController', function($scope) {  
      
      $scope.getMessage = function() {  
        setTimeout(function() {  
          $scope.message = 'Fetched after 3 seconds';  
          console.log('message:'+$scope.message);  
        }, 2000);  
      }  
        
      $scope.getMessage();  
      
    });  

通過運行這個例子特占,你會看到過了兩秒鐘之后是目,控制臺確實會顯示出已經更新的model标捺,然而,view并沒有更新嗤疯。原因也許你已經知道了茂缚,就是我們忘了調用$apply()方法。因此脚囊,我們需要修改getMessage()悔耘,如下所示:

/* What happens with $apply */   
angular.module('myApp',[]).controller('MessageController', function($scope) {  
      
      $scope.getMessage = function() {  
        setTimeout(function() {  
          $scope.$apply(function() {  
            //wrapped this within $apply  
            $scope.message = 'Fetched after 3 seconds';   
            console.log('message:' + $scope.message);  
          });  
        }, 2000);  
      }  
        
      $scope.getMessage();  
      
    });  

如果你運行了上面的例子所意,你會看到view在兩秒鐘之后也會更新扶踊。唯一的變化是我們的代碼現(xiàn)在被wrapped到了$scope.$apply()中,它會自動觸發(fā)$rootScope.$digest()备籽,從而讓watchers被觸發(fā)用以更新view分井。

Note:順便提一下,你應該使用$timeout service來代替setTimeout()尺锚,因為前者會幫你調用$apply()瘫辩,讓你不需要手動地調用它。

而且承绸,注意在以上的代碼中你也可以在修改了model之后手動調用沒有參數(shù)的$apply()挣轨,就像下面這樣:

$scope.getMessage = function() {  
  setTimeout(function() {  
    $scope.message = 'Fetched after two seconds';  
    console.log('message:' + $scope.message);  
    $scope.$apply(); //this triggers a $digest  
  }, 2000);  
};  

以上的代碼使用了$apply()的第二種形式卷扮,也就是沒有參數(shù)的形式。需要記住的是你總是應該使用接受一個function作為參數(shù)的$apply()方法衔瓮。這是因為當你傳入一個function到$apply()中的時候热鞍,這個function會被包裝到一個try…catch塊中衔彻,所以一旦有異常發(fā)生,該異常會被$exceptionHandler service處理澄港。

$digest循環(huán)會運行多少次回梧?

當一個$digest循環(huán)運行時,watchers會被執(zhí)行來檢查scope中的models是否發(fā)生了變化狱意。如果發(fā)生了變化详囤,那么相應的listener函數(shù)就會被執(zhí)行。這涉及到一個重要的問題隆箩。如果listener函數(shù)本身會修改一個scope model呢捌臊?AngularJS會怎么處理這種情況兜材?

答案是$digest循環(huán)不會只運行一次。在當前的一次循環(huán)結束后矾端,它會再執(zhí)行一次循環(huán)用來檢查是否有models發(fā)生了變化卵皂。這就是臟檢查(Dirty Checking)灯变,它用來處理在listener函數(shù)被執(zhí)行時可能引起的model變化添祸。因此滚粟,$digest循環(huán)會持續(xù)運行直到model不再發(fā)生變化凡壤,或者$digest循環(huán)的次數(shù)達到了10次亚侠。因此俗扇,盡可能地不要在listener函數(shù)中修改model铜幽。

Note: $digest循環(huán)最少也會運行兩次串稀,即使在listener函數(shù)中并沒有改變任何model厨诸。正如上面討論的那樣,它會多運行一次來確保models沒有變化颤陶。

結語

我希望這篇文章解釋清楚了$apply和$digest陷遮。需要記住的最重要的是AngularJS是否能檢測到你對于model的修改。如果它不能檢測到帽馋,那么你就需要手動地調用$apply()。

原文地址
http://www.sitepoint.com/understanding-angulars-apply-digest/
http://blog.csdn.net/dm_vincent/article/details/38705099

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末绽族,一起剝皮案震驚了整個濱河市姨涡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吧慢,老刑警劉巖涛漂,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異检诗,居然都是意外死亡匈仗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門逢慌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悠轩,“玉大人,你說我怎么就攤上這事攻泼』鸺埽” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長洞翩。 經常有香客問我已亥,道長,這世上最難降的妖魔是什么捆姜? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任珊豹,我火速辦了婚禮,結果婚禮上忽妒,老公的妹妹穿的比我還像新娘。我一直安慰自己鸯檬,他們只是感情好,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布孽亲。 她就那樣靜靜地躺著栖茉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惶凝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天箱舞,我揣著相機與錄音,去河邊找鬼电湘。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的劫拢。 我是一名探鬼主播偶洋,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吗铐!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤玄渗,失蹤者是張志新(化名)和其女友劉穎浴滴,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體品嚣,經...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年球拦,在試婚紗的時候發(fā)現(xiàn)自己被綠了坎炼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谣光。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情钮糖,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏络它。R本人自食惡果不足惜迂烁,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一黄橘、第九天 我趴在偏房一處隱蔽的房頂上張望子巾。 院中可真熱鬧仪搔,春花似錦煮嫌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挂签,已是汗流浹背侨核。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留逼庞,地道東北人璧南。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓拍柒,卻偏偏與公主長得像种呐,于是被迫代替她去往敵國和親嘿架。 傳聞我的和親對象是個殘疾皇子蝉娜,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內容