原文出處
臟數據檢查 != 輪詢檢查更新
談起angular的臟檢查機制(dirty-checking)
, 常見的誤解就是認為: ng是定時輪詢去檢查model是否變更。
其實,ng只有在指定事件觸發后,才進入$digest cycle
:
- DOM事件,譬如用戶輸入文本,點擊按鈕等。(
ng-click
) - XHR響應事件 (
$http
) - 瀏覽器Location變更事件 (
$location
) - Timer事件(
$timeout
,$interval
) - 執行
$digest()
或$apply()
參考《mastering web application development with angularjs》 P294
$digest后批量更新UI
傳統的JS MVC框架, 數據變更是通過setter去觸發事件,然后立即更新UI。
而angular則是進入$digest cycle
,等待所有model都穩定后,才批量一次性更新UI。
這種機制能減少瀏覽器repaint次數,從而提高性能。
參考《mastering web application development with angularjs》 P296
另, 推薦閱讀: 構建自己的AngularJS,第一部分:Scope和Digest
提速 $digest cycle
關鍵點
- 盡少的觸發$digest (P310)
- 盡快的執行$digest
優化$watch
$scope.$watch(watchExpression, modelChangeCallback)
, watchExpression可以是String或Function。- 避免watchExpression中執行耗時操作,因為它在每次$digest都會執行1~2次。
- 避免watchExpression中操作dom,因為它很耗時。
console.log
也很耗時,記得發布時干掉它。(用grunt groundskeeper)ng-if vs ng-show
, 前者會移除DOM和對應的watch- 及時移除不必要的$watch。(angular自動生成的可以通過下文介紹的
bindonce
)參考《mastering web application development with angularjs》 P303~309
var unwatch = $scope.$watch("someKey", function(newValue, oldValue){ //do sth... if(someCondition){ //當不需要的時候,及時移除watch unwatch(); } });
-
避免深度watch, 即第三個參數為true
參考《mastering web application development with angularjs》 P313
-
減少watch的變量長度
如下,angular不會僅對{{variable}}
建立watcher,而是對整個p標簽。
雙括號應該被span包裹,因為watch的是外部element參考《mastering web application development with angularjs》 P314
<p>plain text other {{variable}} plain text other</p> //改為: <p>plain text other <span ng-bind='variable'></span> plain text other</p> //或 <p>plain text other <span>{{variable}}</span> plain text other</p>
$apply vs $digest
- $apply會使ng進入
$digest cycle
, 並從$rootScope開始遍歷(深度優先)檢查數據變更。 - $digest僅會檢查該scope和它的子scope,當你確定當前操作僅影響它們時,用$digest可以稍微提升性能。
參考《mastering web application development with angularjs》 P308
延遲執行
- 一些不必要的操作,放到
$timeout
里面延遲執行。 - 如果不涉及數據變更,還可以加上第三個參數false,避免調用
$apply
。 - 對時間有要求的,第二個參數可以設置為0。
$http.get('http://path/to/url').success(function(data){ $scope.name = data.name; $timeout(function(){ //do sth later, such as log }, 0, false); });
$evalAsync
vs$timeout
- http://stackoverflow.com/questions/17301572/angularjs-evalasync-vs-timeout
- directive中執行的
$evalAsync
, 會在angular操作DOM之后,瀏覽器渲染之前執行。 - controller中執行的
$evalAsync
, 會在angular操作DOM之前執行,一般不這么用。 - 而使用
$timeout
,會在瀏覽器渲染之后執行。
優化ng-repeat
限制列表個數
- 列表對象的數據轉換,在放入scope之前處理。如
$scope.dataList = convert(dataFromServer)
- 可以使用ngInfiniteScroll來做無限滾動。
使用 track by
刷新數據時,我們常這么做:$scope.tasks = data || [];
,這會導致angular移除掉所有的DOM,重新創建和渲染。
若優化為ng-repeat="task in tasks track by task.id
后,angular就能復用task對應的原DOM進行更新,減少不必要渲染。
參見:http://www.codelord.net/2014/04/15/improving-ng-repeat-performance-with-track-by
使用單次綁定
我們都知道angular建議一個頁面最多2000個雙向綁定,但在列表頁面通常很容易超標。
譬如一個滑動到底部加載下頁的表格,一行20+個綁定, 展示個100行就超標了。
下圖這個只是一個很簡單的列表,還不是表格,就已經這么多個了:
但其實很多屬性顯示后是幾乎不會變更的, 這時候就沒必要雙向綁定了。(不知道angular為何不考慮此類場景)
如下圖,改為bindonce或angular-once后減少了很多:
update:
1.3.0b10開始支持內建單次綁定, {{::variable}}
設計文檔:http://t.cn/RvIYHp9
commit: http://t.cn/RvIYHpC
目前該特性的性能似乎還有待優化(2x slower)
慎用filter
在$digest過程中,filter會執行很多次,至少兩次。
所以要避免在filter中執行耗時操作。
參考《mastering web application development with angularjs》 P136
angular.module('filtersPerf', []).filter('double', function(){ return function(input) { //至少輸出兩次 console.log('Calling double on: '+input); return input + input; }; });
可以在controller中預先處理
//mainCtrl.js angular.module('filtersPerf', []).controller('mainCtrl', function($scope, $filter){ $scope.dataList = $filter('double')(dataFromServer); });
慎用事件
- 減少事件廣播,使用雙向數據綁定或共享service等方法來代替。
$broadcast
會遍歷scope和它的子scope,而不是只通知注冊了該事件的子scope。- 一個優化方式是使用
$emit
, 參見https://github.com/angular/angular.js/issues/4574 - 1.2.7版本對事件做過一個優化,參見https://github.com/angular/angular.js/blob/master/CHANGELOG.md#127-emoji-clairvoyance-2014-01-03
- 對高頻的事件做緩沖限速,避免觸發太頻繁。
directive
- 跟scope數據無關的操作放在compile階段,它只執行一次。
- 除了directive外其他地方,特別是controller里面不要操作dom, 尤其是綁定到scope后,便是災難。
- 改變以前使用JQuery那樣
以DOM為中心
的思維,擁抱以數據為中心
的思維。參見
使用Batarang來分析性能
- AngularJS Batarang是官方提供的chrome插件