AngularJS and scope.$apply

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í),那就是必要條件了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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