全面解析Angular中$Apply()及$Digest()的區(qū)別
$apply()和$digest()在AngularJS中是兩個核心概念,但是有時候它們又讓人困惑。而為了了解AngularJS的工作方式,首先需要了解$apply()和$digest()是如何工作的。這篇文章旨在解釋$apply()和$digest()是什么,以及在日常的編碼中如何應(yīng)用它們。
1、探索$apply()和$digest()
1.1、認(rèn)識雙向數(shù)據(jù)綁定和$watch();
AngularJS提供了一個非??岬奶匦越凶鲭p向數(shù)據(jù)綁定(Two-way Data Binding),這個特性大大簡化了我們的代碼編寫方式。數(shù)據(jù)綁定意味著當(dāng)View中有任何數(shù)據(jù)發(fā)生了變化,那么這個變化也會自動地反饋到scope的數(shù)據(jù)上,也即意味著scope模型會自動地更新。類似地,當(dāng)scope模型發(fā)生變化時,view中的數(shù)據(jù)也會更新到最新的值。那么AngularJS是如何做到這一點(diǎn)的呢?當(dāng)你寫下表達(dá)式如{{ aModel }}時,AngularJS在幕后會為你在scope模型上設(shè)置一個watcher,它用來在數(shù)據(jù)發(fā)生變化的時候更新view。這里的watcher和你會在AngularJS中設(shè)置的watcher是一樣的:
$scope.$watch(‘a(chǎn)Model', function(newValue, oldValue) { //update the DOM with newValue });
傳入到$watch()中的第二個參數(shù)是一個回調(diào)函數(shù),該函數(shù)在aModel的值發(fā)生變化的時候會被調(diào)用。當(dāng)aModel發(fā)生變化的時候,這個回調(diào)函數(shù)會被調(diào)用來更新view這一點(diǎn)不難理解,但是,還存在一個很重要的問題!AngularJS是如何知道什么時候要調(diào)用這個回調(diào)函數(shù)呢?換句話說,AngularJS是如何知曉aModel發(fā)生了變化,才調(diào)用了對應(yīng)的回調(diào)函數(shù)呢?它會周期性的運(yùn)行一個函數(shù)來檢查scope模型中的數(shù)據(jù)是否發(fā)生了變化嗎?好吧,這就是$digest循環(huán)的用武之地了。
在$digest循環(huán)中,watchers會被觸發(fā)。當(dāng)一個watcher被觸發(fā)時,AngularJS會檢測scope模型,如何它發(fā)生了變化那么關(guān)聯(lián)到該watcher的回調(diào)函數(shù)就會被調(diào)用。那么,下一個問題就是$digest循環(huán)是在什么時候以各種方式開始的?
在調(diào)用了$scope.$digest()后,$digest循環(huán)就開始了。假設(shè)你在一個ng-click指令對應(yīng)的handler函數(shù)中更改了scope中的一條數(shù)據(jù),此時AngularJS會自動地通過調(diào)用$digest()來觸發(fā)一輪$digest循環(huán)。當(dāng)$digest循環(huán)開始后,它會觸發(fā)每個watcher。這些watchers會檢查scope中的當(dāng)前model值是否和上一次計(jì)算得到的model值不同。如果不同,那么對應(yīng)的回調(diào)函數(shù)會被執(zhí)行。調(diào)用該函數(shù)的結(jié)果,就是view中的表達(dá)式內(nèi)容(譯注:諸如{{ aModel }})會被更新。除了ng-click指令,還有一些其它的built-in指令以及服務(wù)來讓你更改models(比如ng-model,$timeout等)和自動觸發(fā)一次$digest循環(huán)。
目前為止還不錯!但是,有一個小問題。在上面的例子中,AngularJS并不直接調(diào)用$digest(),而是調(diào)用$scope.$apply(),后者會調(diào)用$rootScope.$digest()。因此,一輪$digest循環(huán)在$rootScope開始,隨后會訪問到所有的children scope中的watchers。
Note: $scope.$apply()會自動地調(diào)用$rootScope.$digest()。
$apply()方法有兩種形式:
第一種會接受一個function作為參數(shù),執(zhí)行該function并且觸發(fā)一輪$digest循環(huán)。
第二種會不接受任何參數(shù),只是觸發(fā)一輪$digest循環(huán)。我們馬上會看到為什么第一種形式更好。
1.2、什么時候手動調(diào)用$apply()方法?
如果AngularJS總是將我們的代碼wrap到一個function中并傳入$apply(),以此來開始一輪$digest循環(huán),那么什么時候才需要我們手動地調(diào)用$apply()方法呢?實(shí)際上,AngularJS對此有著非常明確的要求,就是它只負(fù)責(zé)對發(fā)生于AngularJS上下文環(huán)境中的變更會做出自動地響應(yīng)(即,在$apply()方法中發(fā)生的對于models的更改)。AngularJS的built-in指令就是這樣做的,所以任何的model變更都會被反映到view中。但是,如果你在AngularJS上下文之外的任何地方修改了model,那么你就需要通過手動調(diào)用$apply()來通知AngularJS。這就像告訴AngularJS,你修改了一些models,希望AngularJS幫你觸發(fā)watchers來做出正確的響應(yīng)。
比如,如果你使用了JavaScript中的setTimeout()來更新一個scope model,那么AngularJS就沒有辦法知道你更改了什么。這種情況下,調(diào)用$apply()就是你的責(zé)任了,通過調(diào)用它來觸發(fā)一輪$digest循環(huán)。類似地,如果你有一個指令用來設(shè)置一個DOM事件listener并且在該listener中修改了一些models,那么你也需要通過手動調(diào)用$apply()來確保變更會被正確的反映到view中。
讓我們來看一個例子。加入你有一個頁面,一旦該頁面加載完畢了,你希望在兩秒鐘之后顯示一條信息。你的實(shí)現(xiàn)可能是下面這個樣子的:
html:
<body ng-app=“myApp”> <div ng-controller=“MessageController”> Delayed Message: {{message}} </div> </body>
JavaScript:
/* 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(); });
通過運(yùn)行這個例子,你會看到過了兩秒鐘之后,控制臺確實(shí)會顯示出已經(jīng)更新的model,然而,view并沒有更新。原因也許你已經(jīng)知道了,就是我們忘了調(diào)用$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(); });
如果你運(yùn)行了上面的例子,你會看到view在兩秒鐘之后也會更新。唯一的變化是我們的代碼現(xiàn)在被wrapped到了$scope.$apply()中,它會自動觸發(fā)$rootScope.$digest(),從而讓watchers被觸發(fā)用以更新view。
Note:順便提一下,你應(yīng)該使用$timeout service來代替setTimeout(),因?yàn)榍罢邥湍阏{(diào)用$apply(),讓你不需要手動地調(diào)用它。
而且,注意在以上的代碼中你也可以在修改了model之后手動調(diào)用沒有參數(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ù)的形式。需要記住的是你總是應(yīng)該使用接受一個function作為參數(shù)的$apply()方法。這是因?yàn)楫?dāng)你傳入一個function到$apply()中的時候,這個function會被包裝到一個try…catch塊中,所以一旦有異常發(fā)生,該異常會被$exceptionHandler service處理。
使用 $apply()的情況如下:
•通??梢砸蕾囉贏ngular提供的可用于視圖中的任意指令來調(diào)用 $apply() 。所有 ng-[event]指令(比如 ng-click 、 ng-keypress )都會調(diào)用 $apply() 。
•此外還可以依賴于一系列Angular內(nèi)置的服務(wù)來調(diào)用 $digest() 。比如 $http 服務(wù)會在XHR請求完成并觸發(fā)更新返回值(promise)之后調(diào)用 $apply() 。
•無論何時我們手動處理事件,使用第三方框架(比如jQuery、Facebook API) ,或者調(diào)用setTimeout() ,都可以使用 $apply() 函數(shù)讓Angular返回 $digest 循環(huán)。
調(diào)用setTimeout():
<!DOCTYPE html> <html ng-app="myApp"> <head> <title>$scope.$apply()用法</title> <meta charset="utf-8"> <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script> </head> <body> <div id="div1" ng-controller="mytext"> <div>{{text}}</div> <input id="btn" type="button" value="jquery-event"></input> </div> </body> </html> <script type="text/javascript"> var myModule = angular.module('myApp', []); myModule.controller("mytext",function($scope){ $scope.text = "place"; setTimeout(function(){ $scope.text = "value setted after time out"; $scope.$apply();//必需手動進(jìn)行臟值檢測,否則數(shù)據(jù)無法刷新到界面 },1000); }); </script>
使用第三方框架(比如jQuery、Facebook API):
<!DOCTYPE html> <html ng-app="myApp"> <head> <title>$scope.$apply()用法</title> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/jquery/3.1.0/jquery.min.js"></script> <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script> </head> <body> <div id="div1" ng-controller="mytext"> <div>{{text}}</div> <input id="btn" type="button" value="jquery-event"></input> </div> </body> </html> <script type="text/javascript"> var myModule = angular.module('myApp', []); myModule.controller("mytext",function($scope){ $scope.text = "place"; }); $(function(){ $("#btn").click(function(){ var $scope = $("#btn").scope(); $scope.text = "value setted in jquery"; $scope.$apply(); }); }) </script>
1.3、$digest循環(huán)會運(yùn)行多少次?
當(dāng)一個$digest循環(huán)運(yùn)行時,watchers會被執(zhí)行來檢查scope中的models是否發(fā)生了變化。如果發(fā)生了變化,那么相應(yīng)的listener函數(shù)就會被執(zhí)行。這涉及到一個重要的問題。如果listener函數(shù)本身會修改一個scope model呢?AngularJS會怎么處理這種情況?
答案是$digest循環(huán)不會只運(yùn)行一次。在當(dāng)前的一次循環(huán)結(jié)束后,它會再執(zhí)行一次循環(huán)用來檢查是否有models發(fā)生了變化。這就是臟檢查(Dirty Checking),它用來處理在listener函數(shù)被執(zhí)行時可能引起的model變化。因此,$digest循環(huán)會持續(xù)運(yùn)行直到model不再發(fā)生變化,或者$digest循環(huán)的次數(shù)達(dá)到了10次。因此,盡可能地不要在listener函數(shù)中修改model。
Note: $digest循環(huán)最少也會運(yùn)行兩次,即使在listener函數(shù)中并沒有改變?nèi)魏蝝odel。正如上面討論的那樣,它會多運(yùn)行一次來確保models沒有變化。
結(jié)語
需要記住的最重要的是AngularJS是否能檢測到你對于model的修改。如果它不能檢測到,那么你就需要手動地調(diào)用$apply()。
如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
- angularJS中$apply()方法詳解
- 淺談angular.js中實(shí)現(xiàn)雙向綁定的方法$watch $digest $apply
- AngularJS中的$watch(),$digest()和$apply()區(qū)分
- AngularJS中$apply方法和$watch方法用法總結(jié)
- AngularJS報錯$apply already in progress的解決方法分析
- Angular項(xiàng)目中$scope.$apply()方法的使用詳解
- angularjs 中$apply,$digest,$watch詳解
- Angular.js中$apply()和$digest()的深入理解
- AngularJS雙向數(shù)據(jù)綁定原理之$watch、$apply和$digest的應(yīng)用
- Angularjs中的$apply及優(yōu)化使用詳解
相關(guān)文章
詳解angular2采用自定義指令(Directive)方式加載jquery插件
本篇文章主要介紹了詳解angular2采用自定義指令(Directive)方式加載jquery插件 ,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-02-02AngularJS入門教程之?dāng)?shù)據(jù)綁定用法示例
這篇文章主要介紹了AngularJS之?dāng)?shù)據(jù)綁定用法,結(jié)合實(shí)例形式分析了AngularJS基于內(nèi)置指令ng-model實(shí)現(xiàn)數(shù)據(jù)綁定的操作技巧,需要的朋友可以參考下2016-11-11Angularjs自定義指令實(shí)現(xiàn)分頁插件(DEMO)
由于最近的一個項(xiàng)目使用的是angularjs1.0的版本,涉及到分頁查詢數(shù)據(jù)的功能,后來自己就用自定義指令實(shí)現(xiàn)了該功能,下面小編把實(shí)例demo分享到腳本之家平臺,需要的朋友參考下2017-09-09angularjs學(xué)習(xí)筆記之三大模塊(modal,controller,view)
本文給大家記錄的是angularjs的三大模塊(modal,controller,view)的使用說明,方便初學(xué)者能夠順利的學(xué)習(xí)angularjs.2015-09-09angularjs項(xiàng)目的頁面跳轉(zhuǎn)如何實(shí)現(xiàn)(5種方法)
本篇文章主要介紹了詳解angularjs項(xiàng)目的頁面跳轉(zhuǎn)如何實(shí)現(xiàn) ,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05詳解如何為你的angular app構(gòu)建一個第三方庫
這篇文章主要介紹了詳解如何為你的angular app構(gòu)建一個第三方庫,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12