Angularjs的$apply及其優化使用

北辰狼月發表於2019-03-01

今天,我們要聊得是Angularjs中的小明星$apply。當我們資料更新了,但是view層卻沒反應時,總能聽到有人說,用apply吧,然後,懵懂無知的我們,在賦值程式碼後面加了$scope.$apply(),然後就驚喜的發現。噢,真的更新了。
然而,有些時候,編譯器會無情的給你返回

Error: $digest already in progress
複製程式碼

那麼,導致這些現象的原因時什麼的呢?$apply究竟幹了啥?聽我慢慢道來。

一.$apply的作用

$apply()函式可以從Angular框架的外部讓表示式在Angular上下文內部執行。

上面是AngularJs權威教程中的一句話。什麼意思呢?
首先,你要清楚,在原生js或者第三方框架下,修改model,是有可能不會觸發檢視更新的,比如setTimeout、jquery外掛。為什麼?因為他們脫離了Angularjs的上下文,Angularjs並不能監聽到資料的改變。看例子。

1.setTimeout

html:

<p>{{name}}</p>
複製程式碼

js:

$scope.name="張三";
setTimeout(function(){
$scope.name = `李四`;
//$scope.$apply()
},500)
複製程式碼

首先,name等於張三,500ms後,我把他賦值為李四,但是,頁面上並沒有改變,依然是張三。
而,我們把$scope.$apply()放開,就正常了,張三成功變為李四。

2.第三方外掛

html:

<p>Date: <input type="text" id="datepicker"></p>
<p>
<header>所選日期</header>
{{selectedDate}}
</p>
複製程式碼

js:

$scope.selectedDate = ``;
$( function() {
    $( "#datepicker" ).datepicker({
    onClose: function( selectedDate ) {
        $scope.selectedDate = selectedDate;
        // $scope.$apply();
    }
    });
} );
複製程式碼

這是jquery的datepicker外掛,當我們選定日期後,下面的日期應該隨之顯現,而現在卻沒有。這種情況就必須依靠$apply(),才能更新檢視。

以上兩種情況,都因為不處於Angularjs上下文中,導致監聽不到資料的變化。而$apply究竟幹了什麼,才導致資料更新正常了呢?

其實$apply相當於一個觸發器,它的作用就是觸發digest迴圈,從而更新檢視。

在digest是Angularjs的核心,是它實現了神奇的資料繫結。凡是觸發事件,必會觸發digest迴圈,比如,我們數值的ng事件,click啊,change,實際上都是觸發了digest迴圈。

所以,我們所做的事,其實就是手動觸發了digest迴圈。關於digest迴圈,屬於題外話,這裡不做過多介紹,想深入瞭解的同學,可以看看書籍,或者百度。

二.更好地運用digest迴圈

在Angularjs中,除了$apply可以觸發digest迴圈外,還有其他的方法,也可以觸發此迴圈。而且$apply往往時最壞的選擇。下面推薦一些更好的選擇。

1.$digest

$scope.$digest()的速度要比$apply要快,因為它只更新當前作用域和子作用域的值,對於父作用域時不管的。而$apply還要評估父作用域,這就大大消耗了效能。

2.$timeout

用$timeout去代替你的setTimeout,$timeout作為Angularjs的自帶服務,當然時更契合Angularjs環境啦。它會隱性觸發digest迴圈,而且它會延遲執行,會在上一個digest迴圈完成後的下一刻,觸發digest迴圈,這樣就不會出現上文所說的

$digest already in progress
複製程式碼

我們把setTime的程式碼放到$timeout中

$timeout(function(){
$scope.name = `李四`;
},500)
複製程式碼

這就能正常工作了,看,沒有討厭的apply了!

3.$evalAsync

最推薦的應該時這個方法了。如果當前正好有一個digest迴圈在執行,那麼它就會把導致digest迴圈的操作,放到當前digest迴圈中去執行。而$timeout是要等到當前digest迴圈執行完,再執行一次digest迴圈才可以。所以evalAsync執行更快,效能更好。我們可以像$timeout那樣去呼叫它,即

$scope.$evalAsync(
                    function( $scope ) {
                        console.log( "$evalAsync" );
                    }
                );
複製程式碼

以上,就是今天要說的全部內容。Angularjs中還藏著許多奧祕,和更好的使用方法,希望大家可以深入地研究,分享出更好的文章。
下面是可執行的程式碼,大家可以探究探究

codepen.io/hanwolfxue/…

相關文章