不知不覺镶柱,在項(xiàng)目中用angular已經(jīng)半年多了型豁,踩了很多坑酸些。 趁著放假壮吩,把a(bǔ)ngular的3本書都看了遍,結(jié)合這半年的經(jīng)驗(yàn)贯城,是該做個(gè)總結(jié)了熊楼。 希望可以給大家?guī)?lái)啟示,少踩點(diǎn)坑能犯。
本文針對(duì)的讀者:
- 具備JavaScript性能優(yōu)化的相關(guān)知識(shí)( 雅虎14條性能優(yōu)化原則鲫骗、《高性能網(wǎng)站建設(shè)指南》等)
- 擁有angular實(shí)戰(zhàn)經(jīng)驗(yàn)。
臟數(shù)據(jù)檢查 != 輪詢檢查更新
談起angular的 臟檢查機(jī)制(dirty-checking) , 常見的誤解就是認(rèn)為: ng是定時(shí)輪詢?nèi)z查model是否變更踩晶。 其實(shí)执泰,ng只有在指定事件觸發(fā)后,才進(jìn)入 $digest cycle
- DOM事件渡蜻,譬如用戶輸入文本术吝,點(diǎn)擊按鈕等。( ng-click)
- XHR響應(yīng)事件 ( $http)
- 瀏覽器Location變更事件 ( $location)
- Timer事件( $timeout , $interval)
- 執(zhí)行 $digest()或 $apply()
參考《mastering web application development with angularjs》 P294
$digest后批量更新UI
傳統(tǒng)的JS MVC框架, 數(shù)據(jù)變更是通過(guò)setter去觸發(fā)事件茸苇,然后立即更新UI排苍。 而angular則是進(jìn)入 $digest cycle,等待所有model都穩(wěn)定后学密,才批量一次性更新UI淘衙。 這種機(jī)制能減少瀏覽器repaint次數(shù),從而提高性能腻暮。
參考《mastering web application development with angularjs》 P296
另推薦閱讀: 構(gòu)建自己的AngularJS彤守,第一部分:Scope和Digest
提速 $digest cycle
盡少的觸發(fā)$digest (P310)
盡快的執(zhí)行$digest
優(yōu)化$watch
$scope.$watch(watchExpression, modelChangeCallback) , watchExpression可以是String或Function毯侦。
避免watchExpression中執(zhí)行耗時(shí)操作 ,因?yàn)樗诿看?digest都會(huì)執(zhí)行1~2次具垫。
避免watchExpression中操作dom侈离,因?yàn)樗芎臅r(shí)。
清除console.log
console.log也很耗時(shí)做修,記得發(fā)布時(shí)干掉它霍狰。(用grunt groundskeeper)
ng-if vs ng-show
前者會(huì)移除DOM和對(duì)應(yīng)的watch
及時(shí)移除不必要的$watch抡草。
var unwatch = $scope.$watch("someKey", function(newValue, oldValue){
if(someCondition){
unwatch(); //當(dāng)不需要的時(shí)候,及時(shí)移除watch
}
});
參考《mastering web application development with angularjs》 P303~309
避免深度watch饰及, 即第三個(gè)參數(shù)為true
參考《mastering web application development with angularjs》 P313
減少watch的變量長(zhǎng)度 如下,angular不會(huì)僅對(duì) {{variable}}
建立watcher康震,而是對(duì)整個(gè)p標(biāo)簽燎含。雙括號(hào)應(yīng)該被span包裹,因?yàn)閣atch的是外部element腿短。
<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>
參考《mastering web application development with angularjs》 P314
$apply vs $digest
$apply會(huì)使ng進(jìn)入 $digest cycle, 并從$rootScope開始遍歷(深度優(yōu)先)檢查數(shù)據(jù)變更屏箍。
$digest僅會(huì)檢查該scope和它的子scope,當(dāng)你確定當(dāng)前操作僅影響它們時(shí)橘忱,用$digest可以稍微提升性能赴魁。
參考《mastering web application development with angularjs》 P308
延遲執(zhí)行
一些不必要的操作,放到 $timeout里面延遲執(zhí)行钝诚。
如果不涉及數(shù)據(jù)變更颖御,還可以加上第三個(gè)參數(shù)false,避免調(diào)用 $apply凝颇。
對(duì)時(shí)間有要求的潘拱,第二個(gè)參數(shù)可以設(shè)置為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中執(zhí)行的 $evalAsync拧略,會(huì)在angular操作DOM之后芦岂,瀏覽器渲染之前執(zhí)行。
controller中執(zhí)行的 $evalAsync垫蛆, 會(huì)在angular操作DOM之前執(zhí)行禽最,一般不這么用。
而使用 $timeout袱饭,會(huì)在瀏覽器渲染之后執(zhí)行弛随。
優(yōu)化ng-repeat
限制列表個(gè)數(shù)
列表對(duì)象的數(shù)據(jù)轉(zhuǎn)換,在放入scope之前處理宁赤。如 $scope.dataList = convert(dataFromServer)舀透。
可以使用 ngInfiniteScroll 來(lái)做無(wú)限滾動(dòng)。使用 track by
刷新數(shù)據(jù)時(shí)决左,我們常這么做: $scope.tasks = data || []; 愕够。這會(huì)導(dǎo)致angular移除掉所有的DOM走贪,重新創(chuàng)建和渲染。 若優(yōu)化為 ng-repeat="task in tasks track by task.id后惑芭,angular就能復(fù)用task對(duì)應(yīng)的原DOM進(jìn)行更新坠狡,減少不必要渲染。
參見: http://www.codelord.net/2014/04/15/improving-ng-repeat-performance-with-track-by
- 使用bindonce減少綁定
我們都知道angular建議一個(gè)頁(yè)面最多2000個(gè)雙向綁定遂跟,但在列表頁(yè)面通常很容易超標(biāo)逃沿。 譬如一個(gè)滑動(dòng)到底部加載下頁(yè)的表格,一行20+個(gè)綁定, 展示個(gè)100行就超標(biāo)了幻锁。 但其實(shí)很多屬性顯示后是幾乎不會(huì)變更的凯亮, 這時(shí)候就沒必要雙向綁定了。
慎用filter
在$digest過(guò)程中哄尔,filter會(huì)執(zhí)行很多次假消,至少兩次。 所以要避免在filter中執(zhí)行耗時(shí)操作 岭接。
angular.module('filtersPerf', []).filter('double', function(){
return function(input) {
console.log('Calling double on: '+input); return input + input; //至少輸出兩次
};
});
可以在controller中預(yù)先處理
mainCtrl.jsangular.module('filtersPerf', []).controller('mainCtrl',function($scope, $filter){
$scope.dataList = $filter('double')(dataFromServer);
});
參考《mastering web application development with angularjs》 P136
慎用事件
減少事件廣播富拗,使用雙向數(shù)據(jù)綁定或共享service等方法來(lái)代替。
$broadcast會(huì)遍歷scope和它的子scope鸣戴,而不是只通知注冊(cè)了該事件的子scope啃沪。一個(gè)優(yōu)化方式是使用 $emit.
1.2.7版本對(duì)事件做過(guò)一個(gè)優(yōu)化,
參見 https://github.com/angular/angular.js/blob/master/CHANGELOG.md#127-emoji-clairvoyance-2014-01-03
對(duì)高頻的事件做緩沖限速窄锅,避免觸發(fā)太頻繁创千。
directive
跟scope數(shù)據(jù)無(wú)關(guān)的操作放在compile階段,它只執(zhí)行一次酬滤。
除了directive外其他地方签餐,特別是controller里面不要操作dom, 尤其是綁定到scope后盯串,便是災(zāi)難氯檐。改變以前使用JQuery那樣 以DOM為中心的思維,擁抱以數(shù)據(jù)為中心的思維体捏。
參見: http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background
翻譯: http://blog.jobbole.com/46589/
使用Batarang來(lái)分析性能
AngularJS Batarang 是官方提供的chrome插件
搬運(yùn)自: http://atian25.github.io/2014/05/09/angular-performace/