AngularJS
Profiling und Leistung
Suche…
7 Einfache Leistungsverbesserungen
1) Verwenden Sie ng-repeat sparsam
Die Verwendung von ng-repeat
in Ansichten führt im Allgemeinen zu einer schlechten Leistung, insbesondere bei verschachtelten ng-repeat
-Werten.
Das ist super langsam!
<div ng-repeat="user in userCollection">
<div ng-repeat="details in user">
{{details}}
</div>
</div>
Versuchen Sie, geschachtelte Wiederholungen so weit wie möglich zu vermeiden. Eine Möglichkeit, die Leistung von ng-repeat
zu verbessern ng-repeat
ist die Verwendung von track by $index
(oder einem anderen id-Feld). ng-repeat
verfolgt standardmäßig das gesamte Objekt. Mit track by
überwacht Angular das Objekt nur anhand des $index
oder der Objekt-ID.
<div ng-repeat="user in userCollection track by $index">
{{user.data}}
</div>
Verwenden Sie andere Ansätze wie Seitenumbruch , virtuelle Bildlauf , unendliche Bildlauf oder LimitToOo: Beginnen Sie wann immer möglich, um das Durchlaufen großer Sammlungen zu vermeiden.
2) Einmal binden
Angular hat eine bidirektionale Datenbindung. Es ist mit dem Preis verbunden, langsam zu sein, wenn zu viel verwendet wird.
Langsamere Leistung
<!-- Default data binding has a performance cost -->
<div>{{ my.data }}</div>
Schnellere Leistung (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>
Wenn Sie die "einmal bindend" -Notation verwenden, wird Angular angewiesen, darauf zu warten, dass sich der Wert nach der ersten Serie von Digest-Zyklen stabilisiert. Angular verwendet diesen Wert im DOM und entfernt dann alle Beobachter, sodass dieser zu einem statischen Wert wird und nicht mehr an das Modell gebunden ist.
Das {{}}
ist viel langsamer.
Dieses ng-bind
ist eine Direktive und setzt einen Beobachter auf die übergebene Variable. Das ng-bind
gilt also nur, wenn sich der übergebene Wert tatsächlich ändert.
Die Klammern auf der anderen Seite werden in jedem $digest
überprüft, auch wenn dies nicht notwendig ist.
3) Scope-Funktionen und Filter benötigen Zeit
AngularJS hat eine Digest-Schleife. Alle Ihre Funktionen werden in einer Ansicht angezeigt und Filter werden jedes Mal ausgeführt, wenn der Digest-Zyklus ausgeführt wird. Die Digest-Schleife wird bei jeder Aktualisierung des Modells ausgeführt und kann Ihre App verlangsamen (der Filter kann mehrmals aufgerufen werden, bevor die Seite geladen wird).
Vermeiden dies:
<div ng-controller="bigCalulations as calc">
<p>{{calc.calculateMe()}}</p>
<p>{{calc.data | heavyFilter}}</p>
</div>
Besserer Ansatz
<div ng-controller="bigCalulations as calc">
<p>{{calc.preCalculatedValue}}</p>
<p>{{calc.data | lightFilter}}</p>
</div>
Wo kann der Controller sein:
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 Beobachter
Beobachter sinken die Leistung enorm. Bei mehr Beobachtern dauert die Digest-Schleife länger und die Benutzeroberfläche wird langsamer. Wenn der Beobachter Änderungen erkennt, wird die Digest-Schleife ausgelöst und die Ansicht erneut gerendert.
Es gibt drei Möglichkeiten, die Winkeländerungen in Angular manuell zu überwachen.
$watch()
- $watch()
auf Wertänderungen
$watchCollection()
- $watchCollection()
Änderungen in der Sammlung (Uhren, die mehr als normale $watch
)
$watch(..., true)
- Vermeiden Sie dies so weit wie möglich, es führt "Deep Watch" aus und watchCollection
die Leistung (Uhren mehr als watchCollection
)
Wenn Sie Variablen in der Ansicht binden, erstellen Sie neue Uhren. Verwenden Sie {{::variable}}
, um das Erstellen einer Uhr zu verhindern, insbesondere in Schleifen.
Daher müssen Sie nachverfolgen, wie viele Beobachter Sie verwenden. Sie können die Beobachter mit diesem Skript zählen (Verdienst @Words Like Jared Anzahl der Beobachter )
(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
Diese Funktionen sind im Verhalten sehr ähnlich. ng-if
Elemente aus dem DOM entfernt, während mit ng-show
nur die Elemente ausgeblendet werden, aber alle Handler beibehalten werden. Wenn Sie Teile des Codes haben, die Sie nicht anzeigen möchten, verwenden Sie ng-if
.
Das hängt von der Art der Nutzung ab, aber oft ist einer besser geeignet als der andere.
Wenn das Element nicht benötigt wird, verwenden Sie
ng-if
Um schnell ein- / auszuschalten, verwenden Sie
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>
Im Zweifelsfall ng-if
und testen!
6) Deaktivieren Sie das Debugging
Standardmäßig lassen Bindungsanweisungen und Bereiche zusätzliche Klassen und Markierungen im Code zu, um verschiedene Debugging-Tools zu unterstützen. Wenn Sie diese Option deaktivieren, werden diese verschiedenen Elemente während des Digest-Zyklus nicht mehr gerendert.
angular.module('exampleApp', []).config(['$compileProvider', function ($compileProvider) {
$compileProvider.debugInfoEnabled(false);
}]);
7) Verwenden Sie Abhängigkeitsinjektion, um Ihre Ressourcen verfügbar zu machen
Abhängigkeitsinjektion ist ein Software-Entwurfsmuster, bei dem ein Objekt seine Abhängigkeiten erhält und nicht das Objekt, das es selbst erstellt. Es geht darum, die hartcodierten Abhängigkeiten zu entfernen und sie bei Bedarf jederzeit ändern zu können.
Sie werden sich vielleicht über die mit einer solchen String-Analyse aller injizierbaren Funktionen verbundenen Performance-Kosten wundern. Angular sorgt dafür, indem es die $ inject-Eigenschaft nach dem ersten Mal zwischenspeichert. Das passiert also nicht immer, wenn eine Funktion aufgerufen werden muss.
PRO TIPP: Wenn Sie nach dem Ansatz mit der besten Leistung suchen, verwenden Sie den Annotationsansatz "$ inject property". Dieser Ansatz vermeidet die Analyse der Funktionsdefinition vollständig, da diese Logik innerhalb der folgenden Prüfung in der Anmerkungsfunktion eingeschlossen wird: if (! ($ Inject = fn. $ Inject)). Wenn $ inject bereits verfügbar ist, ist keine Analyse erforderlich!
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 TIPP 2: Sie können eine ng-strict-di
Direktive zu demselben Element wie ng-app
hinzufügen, um den strikten DI-Modus zu aktivieren, der einen Fehler ausgibt, wenn ein Dienst implizite Annotationen verwendet. Beispiel:
<html ng-app="DemoApp" ng-strict-di>
Oder wenn Sie manuelles Bootstrapping verwenden:
angular.bootstrap(document, ['DemoApp'], {
strictDi: true
});
Einmal binden
Angular hat den Ruf, eine hervorragende bidirektionale Datenbindung zu haben. Standardmäßig synchronisiert Angular fortlaufend Werte, die zwischen Modell- und Ansichtskomponenten gebunden sind, wenn sich Daten in der Modell- oder Ansichtskomponente ändern.
Dies ist mit etwas Kosten verbunden, wenn Sie zu viel verwendet werden. Dies hat einen größeren Performance-Erfolg:
Schlechte Leistung: {{my.data}}
Fügen Sie zwei Doppelpunkte ::
vor dem Variablennamen ein, um die einmalige Bindung zu verwenden. In diesem Fall wird der Wert nur aktualisiert, wenn my.data definiert ist. Sie weisen ausdrücklich darauf hin, nicht auf Datenänderungen zu achten. Angular führt keine Wertüberprüfungen durch, sodass bei jedem Digest-Zyklus weniger Ausdrücke ausgewertet werden.
Gute Leistungsbeispiele bei einmaliger Bindung
{{::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>
Hinweis: Dadurch wird jedoch die bidirektionale Datenbindung für my.data
. Wenn sich dieses Feld in Ihrer Anwendung ändert, wird dasselbe nicht automatisch in der Ansicht angezeigt. Verwenden Sie es daher nur für Werte, die sich während der gesamten Lebensdauer Ihrer Anwendung nicht ändern .
Bereichsfunktionen und Filter
AngularJS verfügt über eine Digest-Schleife und alle Ihre Funktionen in einer Ansicht. Filter werden jedes Mal ausgeführt, wenn der Digest-Zyklus ausgeführt wird. Die Digest-Schleife wird bei jeder Aktualisierung des Modells ausgeführt und kann Ihre App verlangsamen (der Filter kann mehrmals aufgerufen werden, bevor die Seite geladen wird).
Sie sollten dies vermeiden:
<div ng-controller="bigCalulations as calc">
<p>{{calc.calculateMe()}}</p>
<p>{{calc.data | heavyFilter}}</p>
</div>
Besserer Ansatz
<div ng-controller="bigCalulations as calc">
<p>{{calc.preCalculatedValue}}</p>
<p>{{calc.data | lightFilter}}</p>
</div>
Wo Controller-Probe ist:
.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
});
Beobachter
Beobachter müssen einen bestimmten Wert überwachen und feststellen, dass dieser Wert geändert wird.
Nach dem Aufruf $watch()
oder $watchCollection
neuer Watcher wird zur internen Watcher-Sammlung im aktuellen Bereich $watchCollection
.
Was ist also ein Beobachter?
Watcher ist eine einfache Funktion, die bei jedem Digest-Zyklus aufgerufen wird und einen Wert zurückgibt. Angular prüft den zurückgegebenen Wert, falls er nicht derselbe ist wie beim vorherigen Aufruf. Ein Rückruf, der im zweiten Parameter an die Funktion $watch()
oder $watchCollection
wird ausgeführt.
(function() {
angular.module("app", []).controller("ctrl", function($scope) {
$scope.value = 10;
$scope.$watch(
function() { return $scope.value; },
function() { console.log("value changed"); }
);
}
})();
Beobachter sind Performancekiller. Je mehr Beobachter Sie haben, desto länger dauert es, bis Sie eine Digest-Schleife erstellen, desto langsamer wird die Benutzeroberfläche. Wenn ein Beobachter Änderungen erkennt, startet er die Digest-Schleife (Neuberechnung auf allen Bildschirmen).
Es gibt drei Möglichkeiten zur manuellen Überwachung der variablen Änderungen in Angular.
$watch()
- achtet nur auf Wertänderungen
$watchCollection()
- $watchCollection()
Änderungen in der Sammlung (Uhren, die mehr als normale $ watch sind)
$watch(..., true)
- Vermeiden Sie dies so weit wie möglich, es wird "Deep Watch" ausgeführt und die Performance wird beendet (Uhren mehr als watchCollection)
Wenn Sie Variablen in der Ansicht binden, erstellen Sie neue Beobachter. Verwenden Sie {{::variable}}
, um keinen Beobachter zu erstellen, insbesondere in Schleifen
Als Ergebnis müssen Sie nachverfolgen, wie viele Beobachter Sie verwenden. Sie können die Beobachter mit diesem Skript zählen (Verdienst @Words) Like Jared - Wie zählt man die Gesamtzahl der Uhren auf einer Seite?
(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);
})();
Wenn Sie kein eigenes Skript erstellen möchten, gibt es ein Open Source-Dienstprogramm namens ng-stats , das ein Echtzeit-Diagramm verwendet, das in die Seite eingebettet ist, um Ihnen einen Einblick in die Anzahl der von Angular verwalteten Uhren sowie in die Häufigkeit und Dauer der Digest-Zyklen über die Zeit. Das Dienstprogramm macht eine globale Funktion mit dem Namen showAngularStats
, die Sie aufrufen können, um zu konfigurieren, wie das Diagramm funktionieren soll.
showAngularStats({
"position": "topleft",
"digestTimeThreshold": 16,
"autoload": true,
"logDigest": true,
"logWatches": true
});
Der obige Beispielcode zeigt die folgende Tabelle automatisch auf der Seite an ( interaktive Demo ).
ng-if vs ng-show
Diese Funktionen sind im Verhalten sehr ähnlich. Der Unterschied ist, dass ng-if
Elemente aus dem DOM entfernt. Wenn große Teile des Codes nicht angezeigt werden, ist ng-if
der richtige Weg. ng-show
blendet nur die Elemente aus, behält aber alle Handler bei.
ng-if
Die Direktive ngIf entfernt oder erstellt einen Teil der DOM-Struktur basierend auf einem Ausdruck. Wenn der Ausdruck ngIf einen falschen Wert ergibt, wird das Element aus dem DOM entfernt. Andernfalls wird ein Klon des Elements erneut in das DOM eingefügt.
ng-show
Die ngShow-Direktive zeigt oder versteckt das angegebene HTML-Element basierend auf dem Ausdruck, der dem ngShow-Attribut bereitgestellt wird. Das Element wird durch Entfernen oder Hinzufügen der CSS-Klasse ng-hide zum Element angezeigt oder ausgeblendet.
Beispiel
<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>
Fazit
Es hängt von der Art der Nutzung, aber oft ist besser geeignet ist als die andere (zB wenn 95% der Zeit das Element nicht benötigt wird, Verwendung ng-if
, wenn Sie das DOM - Element Sichtbarkeit umschalten müssen, verwenden Sie ng-show
).
Im Zweifelsfall ng-if
und testen!
Hinweis : ng-if
erstellt einen neuen isolierten Bereich, während ng-show
und ng-hide
nicht funktionieren. Verwenden Sie $parent.property
wenn die übergeordnete Bereichseigenschaft darin nicht direkt $parent.property
ist.
Enthüllen Sie Ihr 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>
Im obigen Beispiel setzen wir einen Entprellungswert von 1000 Millisekunden (1 Sekunde). Dies ist eine beträchtliche Verzögerung, verhindert jedoch, dass die Eingabe das ng-model
wiederholt mit vielen $digest
Zyklen $digest
.
Durch die Verwendung von Entprellung in Ihren Eingabefeldern und überall dort, wo kein sofortiges Update erforderlich ist, können Sie die Leistung Ihrer Angular-Apps erheblich steigern. Sie können nicht nur zeitlich verzögern, sondern auch, wenn die Aktion ausgelöst wird. Wenn Sie Ihr ng-Modell nicht bei jedem Tastendruck aktualisieren möchten, können Sie auch die Unschärfe aktualisieren.
Heben Sie die Abmeldung der Listener immer auf andere Bereiche als den aktuellen Bereich auf
Sie müssen die Registrierung von Gültigkeitsbereichen außerhalb Ihres aktuellen Gültigkeitsbereichs wie folgt aufheben:
//always deregister these
$rootScope.$on(...);
$scope.$parent.$on(...);
Sie müssen keine Listener im aktuellen Umfang abmelden, da Angle sich darum kümmern würde:
//no need to deregister this
$scope.$on(...);
$rootScope.$on
Listener bleibt im Speicher, wenn Sie zu einem anderen Controller navigieren. Dies führt zu einem Speicherverlust, wenn der Controller außerhalb des gültigen Bereichs liegt.
Nicht
angular.module('app').controller('badExampleController', badExample);
badExample.$inject = ['$scope', '$rootScope'];
function badExample($scope, $rootScope) {
$rootScope.$on('post:created', function postCreated(event, data) {});
}
Tun
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();
});
}