AngularJS
Profilering och prestanda
Sök…
7 enkla prestandaförbättringar
1) Använd ng-repetera sparsamt
Att använda ng-repeat
i vyer resulterar generellt i dålig prestanda, särskilt när det finns kapslade ng-repeat
.
Detta är super långsamt!
<div ng-repeat="user in userCollection">
<div ng-repeat="details in user">
{{details}}
</div>
</div>
Försök att undvika kapslade upprepningar så mycket som möjligt. Ett sätt att förbättra prestanda för ng-repeat
är att använda track by $index
(eller något annat ID-fält). Som standard spårar ng-repeat
hela objektet. Med track by
tittar Angular på objektet endast med $index
eller objekt-id.
<div ng-repeat="user in userCollection track by $index">
{{user.data}}
</div>
Använd andra tillvägagångssätt som pagination , virtuella rullningar , oändliga rullningar eller limitTo: börja när det är möjligt för att undvika upprepning av stora samlingar.
2) Bind en gång
Angular har dubbelriktad databindning. Det kommer med en kostnad för att vara långsam om den används för mycket.
Långsammare prestanda
<!-- Default data binding has a performance cost -->
<div>{{ my.data }}</div>
Snabbare prestanda (AngularJS> = 1.3)
<!-- Bind once is much faster -->
<div>{{ ::my.data }}</div>
<div ng-bind="::my.data"></div>
<!-- Use single binding notation in ng-repeat where only list display is needed -->
<div ng-repeat="user in ::userCollection">
{{::user.data}}
</div>
Med hjälp av noteringen "bind en gång" berättar Angular att vänta på att värdet stabiliseras efter den första serien med smältningscykler. Angular använder det värdet i DOM och tar sedan bort alla tittare så att det blir ett statiskt värde och inte längre är bundet till modellen.
{{}}
Är mycket långsammare.
Denna ng-bind
är ett direktiv och kommer att placera en bevakare på den passerade variabeln. Så ng-bind
kommer endast att gälla när det överförda värdet faktiskt ändras.
Konsolerna å andra sidan blir smutsiga och kontrollerade och uppdaterade i varje $digest
, även om det inte är nödvändigt.
3) Omfångsfunktioner och filter tar tid
AngularJS har en digest-loop. Alla dina funktioner är i en vy och filter körs varje gång matsmältningscykeln körs. Spjällslingan kommer att köras när modellen uppdateras och den kan bromsa din app (filter kan träffas flera gånger innan sidan laddas).
Undvik detta:
<div ng-controller="bigCalulations as calc">
<p>{{calc.calculateMe()}}</p>
<p>{{calc.data | heavyFilter}}</p>
</div>
Bättre tillvägagångssätt
<div ng-controller="bigCalulations as calc">
<p>{{calc.preCalculatedValue}}</p>
<p>{{calc.data | lightFilter}}</p>
</div>
Där regulatorn kan vara:
app.controller('bigCalulations', function(valueService) {
// bad, because this is called in every digest loop
this.calculateMe = function() {
var t = 0;
for(i = 0; i < 1000; i++) {
t += i;
}
return t;
}
// good, because this is executed just once and logic is separated in service to keep the controller light
this.preCalulatedValue = valueService.valueCalculation(); // returns 499500
});
4 tittare
Tittarna släpper enormt prestanda. Med fler tittare kommer smältslingan att ta längre tid och UI kommer att sakta ner. Om bevakaren upptäcker förändring kommer den att starta upp smältslingan och återge vyn.
Det finns tre sätt att manuellt titta på för variabla ändringar i vinkel.
$watch()
- klockor för värdeförändringar
$watchCollection()
- klockor för förändringar i samling (klockor mer än vanliga $watch
)
$watch(..., true)
- Undvik detta så mycket som möjligt, det kommer att utföra "djup klocka" och kommer att minska prestandan (klockor mer än watchCollection
)
Observera att om du är bindande variabler i vyn skapar du nya klockor - använd {{::variable}}
att förhindra att en klocka skapas, särskilt i slingor.
Som ett resultat måste du spåra hur många tittare du använder. Du kan räkna tittarna med det här skriptet (kredit till @Words Like Jared Antal tittare )
(function() {
var root = angular.element(document.getElementsByTagName('body')),
watchers = [],
f = function(element) {
angular.forEach(['$scope', '$isolateScope'], function(scopeProperty) {
if(element.data() && element.data().hasOwnProperty(scopeProperty)) {
angular.forEach(element.data()[scopeProperty].$$watchers, function(watcher) {
watchers.push(watcher);
});
}
});
angular.forEach(element.children(), function(childElement) {
f(angular.element(childElement));
});
};
f(root);
// Remove duplicate watchers
var watchersWithoutDuplicates = [];
angular.forEach(watchers, function(item) {
if(watchersWithoutDuplicates.indexOf(item) < 0) {
watchersWithoutDuplicates.push(item);
}
});
console.log(watchersWithoutDuplicates.length);
})();
5) ng-if / ng-show
Dessa funktioner är väldigt lika i beteende. ng-if
tar bort element från DOM medan ng-show
bara döljer elementen men behåller alla hanterare. Om du har delar av koden som du inte vill visa, använd ng-if
.
Det beror på användningsformen, men ofta är det mer lämpligt än det andra.
Om elementet inte behövs, använd
ng-if
För att snabbt slå på / av, använd
ng-show/ng-hide
<div ng-repeat="user in userCollection"> <p ng-if="user.hasTreeLegs">I am special<!-- some complicated DOM --></p> <p ng-show="user.hasSubscribed">I am awesome<!-- switch this setting on and off --></p> </div>
Om du är osäker - använd ng-if
och test!
6) Inaktivera felsökning
Som standard lämnar bindningsdirektiv och omfattning extra klasser och markering i koden för att hjälpa till med olika felsökningsverktyg. Att inaktivera detta alternativ innebär att du inte längre återger dessa olika element under matsmältningscykeln.
angular.module('exampleApp', []).config(['$compileProvider', function ($compileProvider) {
$compileProvider.debugInfoEnabled(false);
}]);
7) Använd beroendeinjektion för att avslöja dina resurser
Dependency Injection är ett mjukvarudesignmönster där ett objekt ges beroenden snarare än att objektet skapar dem själv. Det handlar om att ta bort de hårdkodade beroenden och göra det möjligt att ändra dem när det behövs.
Du kanske undrar på prestandakostnaderna som är förknippade med en sådan strängparstning av alla injicerbara funktioner. Angular tar hand om detta genom att cachera egenskapen $ injicera efter första gången. Så detta händer inte varje gång en funktion måste åberopas.
PROTIPS: Om du letar efter den strategi som har bästa prestanda, gå med $ injicera egenskapen annotation strategi. Detta tillvägagångssätt undviker helt och hållet funktionsdefinitionen, eftersom denna logik är inslagad i följande kontroll i annotatfunktionen: if (! ($ Inject = fn. $ Inject)). Om $ injektion redan är tillgänglig krävs ingen analys!
var app = angular.module('DemoApp', []);
var DemoController = function (s, h) {
h.get('https://api.github.com/users/angular/repos').success(function (repos) {
s.repos = repos;
});
}
// $inject property annotation
DemoController['$inject'] = ['$scope', '$http'];
app.controller('DemoController', DemoController);
PRO TIPS 2: Du kan lägga till ett ng-strict-di
direktiv om samma element som ng-app
att välja ett strikt DI-läge som kommer att kasta ett fel när en tjänst försöker använda implicita anteckningar. Exempel:
<html ng-app="DemoApp" ng-strict-di>
Eller om du använder manuell bootstrapping:
angular.bootstrap(document, ['DemoApp'], {
strictDi: true
});
Bind en gång
Angular har rykte för att ha en fantastisk dubbelriktad databindning. Som standard synkroniserar Angular kontinuerligt värden som är bundna mellan modell- och visningskomponenter när data ändras i antingen modellen eller visningskomponenten.
Detta kommer med en kostnad för att vara lite långsam om den används för mycket. Detta kommer att få en större prestanda hit:
Dålig prestanda: {{my.data}}
Lägg till två kolon ::
före variabelnamnet för att använda engångsbindning. I detta fall uppdateras värdet endast när my.data har definierats. Du pekar uttryckligen på att inte leta efter dataändringar. Angular kommer inte att utföra några värdekontroller, vilket resulterar i att färre uttryck utvärderas vid varje matsmältningscykel.
Exempel på bra prestanda med engångsbindning
{{::my.data}}
<span ng-bind="::my.data"></span>
<span ng-if="::my.data"></span>
<span ng-repeat="item in ::my.data">{{item}}</span>
<span ng-class="::{ 'my-class': my.data }"></div>
Obs: Detta tar dock bort dubbelriktad my.data
för my.data
, så när detta fält ändras i din applikation kommer det inte att återspeglas automatiskt i vyn. Så använd den bara för värden som inte kommer att ändras under hela ansökan .
Omfångsfunktioner och filter
AngularJS har digest-loop och alla dina funktioner i en vy och filter körs varje gång matningscykeln körs. Spjällslingan kommer att köras när modellen uppdateras och den kan bromsa din app (filter kan träffas flera gånger innan sidan laddas).
Du bör undvika detta:
<div ng-controller="bigCalulations as calc">
<p>{{calc.calculateMe()}}</p>
<p>{{calc.data | heavyFilter}}</p>
</div>
Bättre tillvägagångssätt
<div ng-controller="bigCalulations as calc">
<p>{{calc.preCalculatedValue}}</p>
<p>{{calc.data | lightFilter}}</p>
</div>
Där kontrollerprovet är:
.controller("bigCalulations", function(valueService) {
// bad, because this is called in every digest loop
this.calculateMe = function() {
var t = 0;
for(i = 0; i < 1000; i++) {
t = t + i;
}
return t;
}
//good, because it is executed just once and logic is separated in service to keep the controller light
this.preCalulatedValue = valueService.caluclateSumm(); // returns 499500
});
Tittare
Seare som behövs för att se något värde och upptäcka att detta värde ändras.
Efter samtal $watch()
eller $watchCollection
ny $watchCollection
till den interna vattenskollektionen i nuvarande omfattning.
Så vad är det?
Watcher är en enkel funktion, som kallas för varje smältningscykel och ger ett värde. Vinkel kontrollerar det returnerade värdet, om det inte är detsamma som i det föregående samtalet - en återuppringning som skickades i den andra parametern för att fungera $watch()
eller $watchCollection
kommer att utföras.
(function() {
angular.module("app", []).controller("ctrl", function($scope) {
$scope.value = 10;
$scope.$watch(
function() { return $scope.value; },
function() { console.log("value changed"); }
);
}
})();
Titterna är prestandadödare. Ju fler tittare du har, desto längre tid tar det att göra en smältslinga, desto långsammare användargränssnitt. Om en vaktare upptäcker förändringar kommer den att starta upp smältslingan (omberäkning på hela skärmen)
Det finns tre sätt att göra manuell klocka för variabla ändringar i vinkel.
$watch()
- klockor bara för värdeändringar
$watchCollection()
- klockor för ändringar i samling (klockor mer än vanliga $ klocka)
$watch(..., true)
- Undvik detta så mycket som möjligt, det kommer att utföra "djup klocka" och kommer att döda prestandan (klockor mer än klockansamling)
Observera att om du är bindande variabler i vyn, skapar du nya tittare - använd {{::variable}}
inte skapa vattare, särskilt i slingor
Som ett resultat måste du spåra hur många tittare du använder. Du kan räkna tittarna med det här skriptet (kredit till @Words Like Jared - Hur räknar du totalt antal klockor på en sida?
(function() {
var root = angular.element(document.getElementsByTagName("body")),
watchers = [];
var f = function(element) {
angular.forEach(["$scope", "$isolateScope"], function(scopeProperty) {
if(element.data() && element.data().hasOwnProperty(scopeProperty)) {
angular.forEach(element.data()[scopeProperty].$$watchers, function(watcher) {
watchers.push(watcher);
});
}
});
angular.forEach(element.children(), function(childElement) {
f(angular.element(childElement));
});
};
f(root);
// Remove duplicate watchers
var watchersWithoutDuplicates = [];
angular.forEach(watchers, function(item) {
if(watchersWithoutDuplicates.indexOf(item) < 0) {
watchersWithoutDuplicates.push(item);
}
});
console.log(watchersWithoutDuplicates.length);
})();
Om du inte vill skapa ditt eget skript finns det ett öppet källkodsprogram som heter ng-stats som använder ett realtidsdiagram inbäddat på sidan för att ge dig inblick i antalet klockor som Angular hanterar, liksom frekvens och varaktighet av smälta cykler över tid. Verktyget visar en global funktion som heter showAngularStats
som du kan ringa för att konfigurera hur du vill att diagrammet ska fungera.
showAngularStats({
"position": "topleft",
"digestTimeThreshold": 16,
"autoload": true,
"logDigest": true,
"logWatches": true
});
Exempelkoden ovan visar följande diagram automatiskt på sidan ( interaktiv demo ).
ng-if vs ng-show
Dessa funktioner är mycket lika beteende. Skillnaden är att ng-if
tar bort element från DOM. Om det finns stora delar av koden som inte kommer att visas, är ng-if
vägen att gå. ng-show
döljer bara elementen men kommer att behålla alla hanterare.
ng-om
Direktivet ngIf tar bort eller återskapar en del av DOM-trädet baserat på ett uttryck. Om uttrycket som tilldelas ngIf utvärderar till ett falskt värde tas elementet bort från DOM, annars sätts en klon av elementet tillbaka in i DOM.
ng-show
Direktivet ngShow visar eller döljer det givna HTML-elementet baserat på det uttryck som tillhandahålls till attributet ngShow. Elementet visas eller döljs genom att ta bort eller lägga till ng-hide CSS-klassen i elementet.
Exempel
<div ng-repeat="user in userCollection">
<p ng-if="user.hasTreeLegs">I am special
<!-- some complicated DOM -->
</p>
<p ng-show="user.hasSubscribed">I am aweosme
<!-- switch this setting on and off -->
</p>
</div>
Slutsats
Det beror på vilken typ av användning, men ofta är den mer lämpad än den andra (t.ex. om 95% av tiden elementet inte behövs, använd ng-if
; om du behöver växla DOM-elementets synlighet, använd ng-show
).
ng-if
osäker, använd ng-if
och test!
Obs : ng-if
skapar ett nytt isolerat omfång, medan ng-show
och ng-hide
inte gör det. Använd $parent.property
om egenskapen för överordnad omfattning inte är direkt tillgänglig i den.
Bestäm din modell
<div ng-controller="ExampleController">
<form name="userForm">
Name:
<input type="text" name="userName"
ng-model="user.name"
ng-model-options="{ debounce: 1000 }" />
<button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br />
</form>
<pre>user.name = </pre>
</div>
I exemplet ovan ställer vi in ett avvisningsvärde på 1000 millisekunder, vilket är 1 sekund. Detta är en avsevärd fördröjning, men kommer att förhindra att ingången repeterar ng-model
upprepade gånger med många $digest
.
Genom att använda debounce på dina inmatningsfält och någon annanstans där en omedelbar uppdatering inte krävs, kan du öka prestandan för dina Angular-appar ganska väsentligt. Du kan inte bara försena vid tiden, utan du kan också försena när åtgärden utlöses. Om du inte vill uppdatera din ng-modell på varje tangenttryckning, kan du också uppdatera om oskärpa också.
Avregistrera alltid lyssnare som är registrerade på andra tillämpningsområden än den nuvarande räckvidden
Du måste alltid avregistrera andra områden än ditt nuvarande räckvidd som visas nedan:
//always deregister these
$rootScope.$on(...);
$scope.$parent.$on(...);
Du behöver inte avregistrera listnare på nuvarande omfattning eftersom vinkeln skulle ta hand om det:
//no need to deregister this
$scope.$on(...);
$rootScope.$on
lyssnare kommer att finnas kvar i minnet om du navigerar till en annan controller. Detta skapar en minnesläcka om styrenheten faller utanför räckvidden.
inte
angular.module('app').controller('badExampleController', badExample);
badExample.$inject = ['$scope', '$rootScope'];
function badExample($scope, $rootScope) {
$rootScope.$on('post:created', function postCreated(event, data) {});
}
Do
angular.module('app').controller('goodExampleController', goodExample);
goodExample.$inject = ['$scope', '$rootScope'];
function goodExample($scope, $rootScope) {
var deregister = $rootScope.$on('post:created', function postCreated(event, data) {});
$scope.$on('$destroy', function destroyScope() {
deregister();
});
}