$apply()和$digest()

$apply()和$digest()在AngularJS中是兩個(gè)核心概念,但是有時(shí)候它們又讓人困惑截歉。而為了了解AngularJS的工作方式添忘,首先需要了解$apply()和$digest()是如何工作的算柳。

探索$apply()和$digest()

AngularJS提供了一個(gè)非呈扔酷的特性叫做雙向數(shù)據(jù)綁定(Two-way Data Binding),這個(gè)特性大大簡(jiǎn)化了我們的代碼編寫方式张遭。數(shù)據(jù)綁定意味著當(dāng)View中有任何數(shù)據(jù)發(fā)生了變化邓萨,那么這個(gè)變化也會(huì)自動(dòng)地反饋到scope的數(shù)據(jù)上,也即意味著scope模型會(huì)自動(dòng)地更新菊卷。類似地缔恳,當(dāng)scope模型發(fā)生變化時(shí),view中的數(shù)據(jù)也會(huì)更新到最新的值洁闰。那么AngularJS是如何做到這一點(diǎn)的呢褐耳?當(dāng)你寫下表達(dá)式如{{ aModel }}時(shí),AngularJS在幕后會(huì)為你在scope模型上設(shè)置一個(gè)watcher渴庆,它用來在數(shù)據(jù)發(fā)生變化的時(shí)候更新view。這里的watcher和你會(huì)在AngularJS中設(shè)置的watcher是一樣的:

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

傳入到$watch()中的第二個(gè)參數(shù)是一個(gè)回調(diào)函數(shù)雅镊,該函數(shù)在aModel的值發(fā)生變化的時(shí)候會(huì)被調(diào)用襟雷。當(dāng)aModel發(fā)生變化的時(shí)候,這個(gè)回調(diào)函數(shù)會(huì)被調(diào)用來更新view這一點(diǎn)不難理解仁烹,但是耸弄,還存在一個(gè)很重要的問題!AngularJS是如何知道什么時(shí)候要調(diào)用這個(gè)回調(diào)函數(shù)呢卓缰?換句話說计呈,AngularJS是如何知曉aModel發(fā)生了變化,才調(diào)用了對(duì)應(yīng)的回調(diào)函數(shù)呢征唬?它會(huì)周期性的運(yùn)行一個(gè)函數(shù)來檢查scope模型中的數(shù)據(jù)是否發(fā)生了變化嗎捌显?好吧,這就是$digest循環(huán)的用武之地了总寒。

在$digest循環(huán)中扶歪,watchers會(huì)被觸發(fā)。當(dāng)一個(gè)watcher被觸發(fā)時(shí)摄闸,AngularJS會(huì)檢測(cè)scope模型善镰,如何它發(fā)生了變化那么關(guān)聯(lián)到該watcher的回調(diào)函數(shù)就會(huì)被調(diào)用。那么年枕,下一個(gè)問題就是$digest循環(huán)是在什么時(shí)候以各種方式開始的炫欺?

在調(diào)用了$scope.$digest()后,$digest循環(huán)就開始了熏兄。假設(shè)你在一個(gè)ng-click指令對(duì)應(yīng)的handler函數(shù)中更改了scope中的一條數(shù)據(jù)品洛,此時(shí)AngularJS會(huì)自動(dòng)地通過調(diào)用$digest()來觸發(fā)一輪$digest循環(huán)树姨。當(dāng)$digest循環(huán)開始后,它會(huì)觸發(fā)每個(gè)watcher毫别。這些watchers會(huì)檢查scope中的當(dāng)前model值是否和上一次計(jì)算得到的model值不同娃弓。如果不同,那么對(duì)應(yīng)的回調(diào)函數(shù)會(huì)被執(zhí)行岛宦。調(diào)用該函數(shù)的結(jié)果台丛,就是view中的表達(dá)式內(nèi)容(譯注:諸如{{ aModel }})會(huì)被更新。除了ng-click指令砾肺,還有一些其它的built-in指令以及服務(wù)來讓你更改models(比如ng-model挽霉,$timeout等)和自動(dòng)觸發(fā)一次$digest循環(huán)。

目前為止還不錯(cuò)变汪!但是侠坎,有一個(gè)小問題。在上面的例子中裙盾,AngularJS并不直接調(diào)用$digest()实胸,而是調(diào)用$scope.$apply(),后者會(huì)調(diào)用$rootScope.$digest()番官。因此庐完,一輪$digest循環(huán)在$rootScope開始,隨后會(huì)訪問到所有的children scope中的watchers徘熔。

現(xiàn)在门躯,假設(shè)你將ng-click指令關(guān)聯(lián)到了一個(gè)button上,并傳入了一個(gè)function名到ng-click上酷师。當(dāng)該button被點(diǎn)擊時(shí)讶凉,AngularJS會(huì)將此function包裝到一個(gè)wrapping function中,然后傳入到$scope.$apply()山孔。因此懂讯,你的function會(huì)正常被執(zhí)行,修改models(如果需要的話)台颠,此時(shí)一輪$digest循環(huán)也會(huì)被觸發(fā)域醇,用來確保view也會(huì)被更新。

Note: $scope.$apply()會(huì)自動(dòng)地調(diào)用$rootScope.$digest()蓉媳。$apply()方法有兩種形式譬挚。第一種會(huì)接受一個(gè)function作為參數(shù),執(zhí)行該function并且觸發(fā)一輪$digest循環(huán)酪呻。第二種會(huì)不接受任何參數(shù)减宣,只是觸發(fā)一輪$digest循環(huán)。我們馬上會(huì)看到為什么第一種形式更好玩荠。

什么時(shí)候手動(dòng)調(diào)用$apply()方法漆腌?

如果AngularJS總是將我們的代碼wrap到一個(gè)function中并傳入$apply()贼邓,以此來開始一輪$digest循環(huán),那么什么時(shí)候才需要我們手動(dòng)地調(diào)用$apply()方法呢闷尿?實(shí)際上塑径,AngularJS對(duì)此有著非常明確的要求,就是它只負(fù)責(zé)對(duì)發(fā)生于AngularJS上下文環(huán)境中的變更會(huì)做出自動(dòng)地響應(yīng)(即填具,在$apply()方法中發(fā)生的對(duì)于models的更改)统舀。AngularJS的built-in指令就是這樣做的,所以任何的model變更都會(huì)被反映到view中劳景。但是誉简,如果你在AngularJS上下文之外的任何地方修改了model,那么你就需要通過手動(dòng)調(diào)用$apply()來通知AngularJS盟广。這就像告訴AngularJS闷串,你修改了一些models,希望AngularJS幫你觸發(fā)watchers來做出正確的響應(yīng)筋量。

比如烹吵,如果你使用了JavaScript中的setTimeout()來更新一個(gè)scope model,那么AngularJS就沒有辦法知道你更改了什么桨武。這種情況下肋拔,調(diào)用$apply()就是你的責(zé)任了,通過調(diào)用它來觸發(fā)一輪$digest循環(huán)玻募。類似地,如果你有一個(gè)指令用來設(shè)置一個(gè)DOM事件listener并且在該listener中修改了一些models一姿,那么你也需要通過手動(dòng)調(diào)用$apply()來確保變更會(huì)被正確的反映到view中七咧。

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

1.<body ng-app="myApp">  
2.  <div ng-controller="MessageController">  
3.    Delayed Message: {{message}}  
4.  </div>    
5.</body>  
1./* What happens without an $apply() */  
2.      
3.    angular.module('myApp',[]).controller('MessageController', function($scope) {  
4.      
5.      $scope.getMessage = function() {  
6.        setTimeout(function() {  
7.          $scope.message = 'Fetched after 3 seconds';  
8.          console.log('message:'+$scope.message);  
9.        }, 2000);  
10.      }  
11.        
12.      $scope.getMessage();  
13.      
14.    });  

通過運(yùn)行這個(gè)例子蛉顽,你會(huì)看到過了兩秒鐘之后蝗砾,控制臺(tái)確實(shí)會(huì)顯示出已經(jīng)更新的model,然而携冤,view并沒有更新悼粮。原因也許你已經(jīng)知道了,就是我們忘了調(diào)用$apply()方法曾棕。因此扣猫,我們需要修改getMessage(),如下所示:

1./* What happens with $apply */   
2.angular.module('myApp',[]).controller('MessageController', function($scope) {  
3.      
4.      $scope.getMessage = function() {  
5.        setTimeout(function() {  
6.          $scope.$apply(function() {  
7.            //wrapped this within $apply  
8.            $scope.message = 'Fetched after 3 seconds';   
9.            console.log('message:' + $scope.message);  
10.          });  
11.        }, 2000);  
12.      }  
13.        
14.      $scope.getMessage();  
15.      
16.    });  
 

如果你運(yùn)行了上面的例子翘地,你會(huì)看到view在兩秒鐘之后也會(huì)更新申尤。唯一的變化是我們的代碼現(xiàn)在被wrapped到了$scope.$apply()中癌幕,它會(huì)自動(dòng)觸發(fā)$rootScope.$digest(),從而讓watchers被觸發(fā)用以更新view昧穿。
Note:順便提一下勺远,你應(yīng)該使用$timeout service來代替setTimeout(),因?yàn)榍罢邥?huì)幫你調(diào)用$apply()时鸵,讓你不需要手動(dòng)地調(diào)用它胶逢。
而且,注意在以上的代碼中你也可以在修改了model之后手動(dòng)調(diào)用沒有參數(shù)的$apply()寥枝,就像下面這樣:

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

以上的代碼使用了$apply()的第二種形式宪塔,也就是沒有參數(shù)的形式。需要記住的是你總是應(yīng)該使用接受一個(gè)function作為參數(shù)的$apply()方法囊拜。這是因?yàn)楫?dāng)你傳入一個(gè)function到$apply()中的時(shí)候某筐,這個(gè)function會(huì)被包裝到一個(gè)try…catch塊中,所以一旦有異常發(fā)生冠跷,該異常會(huì)被$exceptionHandler service處理南誊。

$digest循環(huán)會(huì)運(yùn)行多少次?

當(dāng)一個(gè)$digest循環(huán)運(yùn)行時(shí)蜜托,watchers會(huì)被執(zhí)行來檢查scope中的models是否發(fā)生了變化抄囚。如果發(fā)生了變化,那么相應(yīng)的listener函數(shù)就會(huì)被執(zhí)行橄务。這涉及到一個(gè)重要的問題幔托。如果listener函數(shù)本身會(huì)修改一個(gè)scope model呢?AngularJS會(huì)怎么處理這種情況蜂挪?

答案是$digest循環(huán)不會(huì)只運(yùn)行一次重挑。在當(dāng)前的一次循環(huán)結(jié)束后,它會(huì)再執(zhí)行一次循環(huán)用來檢查是否有models發(fā)生了變化棠涮。這就是臟檢查(Dirty Checking)谬哀,它用來處理在listener函數(shù)被執(zhí)行時(shí)可能引起的model變化。因此严肪,$digest循環(huán)會(huì)持續(xù)運(yùn)行直到model不再發(fā)生變化史煎,或者$digest循環(huán)的次數(shù)達(dá)到了10次。因此驳糯,盡可能地不要在listener函數(shù)中修改model篇梭。

Note: $digest循環(huán)最少也會(huì)運(yùn)行兩次,即使在listener函數(shù)中并沒有改變?nèi)魏蝝odel酝枢。正如上面討論的那樣很洋,它會(huì)多運(yùn)行一次來確保models沒有變化。

結(jié)語

我希望這篇文章解釋清楚了$apply和$digest隧枫。需要記住的最重要的是AngularJS是否能檢測(cè)到你對(duì)于model的修改喉磁。如果它不能檢測(cè)到谓苟,那么你就需要手動(dòng)地調(diào)用$apply()。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末协怒,一起剝皮案震驚了整個(gè)濱河市涝焙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌孕暇,老刑警劉巖仑撞,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異妖滔,居然都是意外死亡隧哮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門座舍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沮翔,“玉大人,你說我怎么就攤上這事曲秉〔墒矗” “怎么了?”我有些...
    開封第一講書人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵承二,是天一觀的道長(zhǎng)榆鼠。 經(jīng)常有香客問我,道長(zhǎng)亥鸠,這世上最難降的妖魔是什么妆够? 我笑而不...
    開封第一講書人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮负蚊,結(jié)果婚禮上神妹,老公的妹妹穿的比我還像新娘。我一直安慰自己盖桥,他們只是感情好灾螃,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開白布题翻。 她就那樣靜靜地躺著揩徊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嵌赠。 梳的紋絲不亂的頭發(fā)上塑荒,一...
    開封第一講書人閱讀 49,837評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音姜挺,去河邊找鬼齿税。 笑死,一個(gè)胖子當(dāng)著我的面吹牛炊豪,可吹牛的內(nèi)容都是我干的凌箕。 我是一名探鬼主播拧篮,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼牵舱!你這毒婦竟也來了串绩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬榮一對(duì)情侶失蹤芜壁,失蹤者是張志新(化名)和其女友劉穎礁凡,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慧妄,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡顷牌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了塞淹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窟蓝。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖窖铡,靈堂內(nèi)的尸體忽然破棺而出疗锐,到底是詐尸還是另有隱情,我是刑警寧澤费彼,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布滑臊,位于F島的核電站,受9級(jí)特大地震影響箍铲,放射性物質(zhì)發(fā)生泄漏雇卷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一颠猴、第九天 我趴在偏房一處隱蔽的房頂上張望关划。 院中可真熱鬧,春花似錦翘瓮、人聲如沸贮折。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽调榄。三九已至,卻和暖如春呵扛,著一層夾襖步出監(jiān)牢的瞬間每庆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工今穿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缤灵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像腮出,于是被迫代替她去往敵國(guó)和親帖鸦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容