AngularJS and scope.$apply
翻譯自Jim Hoskins的教學(xué)博客
當(dāng)你開始使用AngularJS開始寫真正的項(xiàng)目的時(shí)候,你一定會碰到$scope
.$apply()
方法。表面上看,這像是一個(gè)幫助你進(jìn)行數(shù)據(jù)更新的方法,那么,它為何存在,我們又該如何使用它呢。
JavaScript是基于順序執(zhí)行的
JavaScript代碼按照代碼片段的順序來之行,每個(gè)代碼塊從運(yùn)行到結(jié)束都不會被打斷,這也是為什么會發(fā)生瀏覽器阻塞的情況,往往是有一部分在運(yùn)行,而導(dǎo)致其他所有的代碼段凍結(jié)。
每當(dāng)有耗費(fèi)時(shí)間較多的任務(wù)出現(xiàn),例如等待一個(gè)click事件,等待Ajax請求的回應(yīng),我們都會設(shè)定一個(gè)回調(diào)函數(shù),當(dāng)click事件被觸發(fā)或者計(jì)時(shí)器完成,就會創(chuàng)建一個(gè)新的JavaScript turn,并執(zhí)行完回調(diào)函數(shù)。
例如:
var button = document.getElementById('clickMe');
function buttonClicked () {
alert('the button was clicked');
}
button.addEventListener('click', buttonClicked);
function timerComplete () {
alert('timer complete');
}
setTimeout(timerComplete, 2000);`
當(dāng)JavaScript代碼開始運(yùn)行,先找到一個(gè)botton,并添加一個(gè)點(diǎn)擊的監(jiān)聽事件,且設(shè)定一個(gè)timeout。瀏覽器會再這段代碼執(zhí)行完畢之后進(jìn)行web的更新,并且接受用戶的輸入。
如果瀏覽器檢測到一個(gè)新的點(diǎn)擊事件發(fā)生,他就會開始一個(gè)turn,來執(zhí)行buttonClicked
函數(shù)。當(dāng)函數(shù)執(zhí)行結(jié)束,這個(gè)階段也隨之結(jié)束。
經(jīng)過2000毫秒,瀏覽器會創(chuàng)建一個(gè)過程來執(zhí)行timerComplete
。在這兩個(gè)過程之間,頁面被重繪,輸入被接收。
如何來更新綁定數(shù)據(jù)
Angular為我們提供了一些接口綁定JavaScript代碼和數(shù)據(jù),那么Angular又是如何知道數(shù)據(jù)改變,并且需要更新的呢。
這里有兩種策略來解決這個(gè)問題。
第一種是使用特殊對象,通過方法來設(shè)定數(shù)據(jù)而不是屬性。這種方法并不推薦,主要是由于其繁瑣的操作。例如不能使用obj.key = 'value'
,而是要使用obj.set('key', 'value')
代替(一些框架如EmberJS和 KnockoutJS還在使用這個(gè)方法。
而Angular采取了另外的解決方案,即允許任何值作為綁定數(shù)據(jù)。在JavaScript運(yùn)行末尾,檢測是否有數(shù)據(jù)的更新,若有,這將這些變化反射到我們綁定的顯示上。
$apply 和 $digest
進(jìn)行數(shù)據(jù)變化檢查的實(shí)際上是$digest
函數(shù),但是我們往往不是直接使用$digest
,而是使用$apply
,$apply
接收表達(dá)式或者函數(shù)作為參數(shù)后調(diào)用$digest來更新綁定部門以及監(jiān)控器。
那我們究竟要什么時(shí)候來調(diào)用$apply
呢,實(shí)際上,Angular幾乎在所有提供的代碼中添加了$apply
,如ng-click
,初始controller,$http
的回調(diào)操作,在這,你并不需要親自調(diào)用 $apply
,而且重復(fù)的調(diào)用會引起錯(cuò)誤。
因此,當(dāng)你運(yùn)行了一個(gè)新階段,并且這部分并不屬于Angular庫的情況下才需要使用$apply
。這有一段關(guān)于setTimeout
的代碼,在經(jīng)過了2000毫秒的延遲之后,代碼進(jìn)入執(zhí)行了一個(gè)新的階段,但是Angular并不知道數(shù)據(jù)有更新,因此更新并不會被顯示。
function Ctrl($scope) {
$scope.message = "Waiting 2000ms for update";
setTimeout(function () {
$scope.message = "Timeout called!";
// AngularJS unaware of update to $scope
}, 2000);
}
為了方便大家的使用,Angular提供了$timeout
來代替setTimeout
,相當(dāng)于在其中默認(rèn)調(diào)用$apply
。
如果在你的代碼中使用了除$http
之外的Ajax調(diào)用,除了ng-*
之外的監(jiān)聽器,或者除了$timeout
之外的計(jì)時(shí)器,都應(yīng)該使用$scope.$apply
來同步顯示綁定。
$scope.$apply() vs $scope.$apply(fn)
最后討論一下$scope.$apply()
和$scope.$apply(fn)
應(yīng)該使用哪個(gè)的問題,相信大家看到過很多使用$scope.$apply()
的例子,通過它確實(shí)可以達(dá)到預(yù)期的結(jié)果,但是也可能錯(cuò)失其他的機(jī)會。
如果你的代碼沒有作為一個(gè)參數(shù)傳入$scope.$apply()
,那么,當(dāng)其發(fā)生異常或者執(zhí)行錯(cuò)誤時(shí),錯(cuò)誤將會拋出到Angular之外,這意味著你的異常處理程序?qū)雎缘羲?shí)際上,$scope.$apply()
不單單是只運(yùn)行你的代碼,同時(shí)它還提供了try/catch
,而$digest
運(yùn)行在一個(gè)finally
語句中,這意味著,無論有錯(cuò)誤與否,
你的程序都會正常執(zhí)行,這是很優(yōu)秀的一個(gè)特性。
當(dāng)僅僅使用Angular所提供的對象,你不該過多的使用$apply()
,而當(dāng)你使用directive直接去操作DOM元素時(shí),那就是必要條件了。
完