Zoeken…


7 Eenvoudige prestatieverbeteringen

1) Gebruik ng-repeat spaarzaam

Het gebruik van ng-repeat in weergaven leidt doorgaans tot slechte prestaties, vooral als er geneste ng-repeat 's zijn.

Dit gaat super langzaam!

<div ng-repeat="user in userCollection">
  <div ng-repeat="details in user">
    {{details}}
  </div>
</div>

Probeer zoveel mogelijk geneste herhalingen te vermijden. Een manier om de prestaties van ng-repeat te verbeteren, is door track by $index (of een ander id-veld) te gebruiken. Standaard volgt ng-repeat het hele object. Met track by , bekijkt Angular het object alleen aan de hand van de $index of object-ID.

<div ng-repeat="user in userCollection track by $index">
  {{user.data}}
</div>

Gebruik andere benaderingen zoals paginering , virtuele scrolls , oneindige scrolls of limitTo: begin waar mogelijk om iteratie over grote collecties te voorkomen.


2) Bind één keer

Angular heeft bidirectionele gegevensbinding. Het heeft de kosten om langzaam te zijn als het te veel wordt gebruikt.

Langzamere prestaties

<!-- Default data binding has a performance cost -->
<div>{{ my.data }}</div>

Hogere prestaties (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>

Met behulp van de notatie "één keer binden" moet Angular wachten tot de waarde stabiliseert na de eerste reeks digestcycli. Angular gebruikt die waarde in de DOM en verwijdert vervolgens alle watchers zodat deze een statische waarde wordt en niet langer aan het model is gebonden.

De {{}} is veel langzamer.

Deze ng-bind is een richtlijn en zal een watcher op de doorgegeven variabele plaatsen. De ng-bind is dus alleen van toepassing als de doorgegeven waarde daadwerkelijk verandert.

De haakjes worden daarentegen vies gecontroleerd en ververst in elke $digest , zelfs als het niet nodig is.


3) Scope-functies en filters kosten tijd

AngularJS heeft een digest-lus. Al uw functies worden weergegeven en filters worden uitgevoerd telkens wanneer de digest-cyclus wordt uitgevoerd. De digest-lus wordt uitgevoerd wanneer het model wordt bijgewerkt en het kan uw app vertragen (filter kan meerdere keren worden geraakt voordat de pagina wordt geladen).

Vermijd dit:

<div ng-controller="bigCalulations as calc">
  <p>{{calc.calculateMe()}}</p>
  <p>{{calc.data | heavyFilter}}</p>
</div>

Betere aanpak

<div ng-controller="bigCalulations as calc">
  <p>{{calc.preCalculatedValue}}</p>
  <p>{{calc.data | lightFilter}}</p>
</div>

Waar de controller kan zijn:

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 kijkers

Kijkers laten de prestaties enorm dalen. Met meer kijkers zal de digest-lus langer duren en de gebruikersinterface vertragen. Als de kijker verandering detecteert, start hij de digest-lus en geeft hij de weergave opnieuw.

Er zijn drie manieren om handmatig te kijken naar variabele wijzigingen in Angular.

$watch() - let op waardewijzigingen

$watchCollection() - let op wijzigingen in collectie (horloges meer dan regulier $watch )

$watch(..., true) - Vermijd dit zoveel mogelijk, het zal "deep watch" uitvoeren en de prestaties verminderen (horloges meer dan watchCollection )

Houd er rekening mee dat als u variabelen in de weergave bindt, u nieuwe horloges maakt - gebruik {{::variable}} om te voorkomen dat u een horloge maakt, vooral in loops.

Daarom moet u bijhouden hoeveel kijkers u gebruikt. U kunt de kijkers met dit script tellen (met dank aan @Words Like Jared Aantal kijkers )

(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

Deze functies lijken erg op elkaar. ng-if verwijdert elementen uit de DOM terwijl ng-show alleen de elementen verbergt maar alle handlers bewaart. Als u delen van de code hebt die u niet wilt weergeven, gebruikt u ng-if .

Het hangt af van het type gebruik, maar vaak is de een geschikter dan de andere.

  • Als het element niet nodig is, gebruikt u ng-if

  • Gebruik ng-show/ng-hide om snel aan / uit te schakelen

    <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>
    

Gebruik bij twijfel ng-if en test!


6) Schakel foutopsporing uit

Bindrichtlijnen en scopes laten standaard extra klassen en markeringen in de code achter om te helpen met verschillende hulpprogramma's voor foutopsporing. Als u deze optie uitschakelt, worden deze verschillende elementen niet meer weergegeven tijdens de digest-cyclus.

angular.module('exampleApp', []).config(['$compileProvider', function ($compileProvider) {
    $compileProvider.debugInfoEnabled(false);
}]);

7) Gebruik afhankelijkheidsinjectie om uw middelen bloot te leggen

Dependency Injection is een software-ontwerppatroon waarin een object zijn afhankelijkheden krijgt, in plaats van het object dat ze zelf maakt. Het gaat om het verwijderen van de hard-gecodeerde afhankelijkheden en het mogelijk maken om deze te wijzigen wanneer dat nodig is.

U vraagt zich misschien af wat de prestatiekosten zijn die gepaard gaan met het parseren van alle injecteerbare functies. Angular zorgt hiervoor door na de eerste keer de eigenschap $ inject in de cache op te slaan. Dit gebeurt dus niet telkens wanneer een functie moet worden aangeroepen.

PRO TIP: Als u op zoek bent naar de aanpak met de beste prestaties, kies dan voor de benadering van de $ inject property-annotatie. Deze benadering vermijdt volledig het parseren van de functiedefinitie omdat deze logica is ingepakt binnen de volgende controle in de annotatiefunctie: if (! ($ Inject = fn. $ Inject)). Als $ inject al beschikbaar is, is parsen niet nodig!

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 TIP 2: Je kunt een ng-strict-di richtlijn toevoegen op hetzelfde element als ng-app om je aan te melden voor de strikte DI-modus die een foutmelding geeft wanneer een service impliciete annotaties probeert te gebruiken. Voorbeeld:

<html ng-app="DemoApp" ng-strict-di>

Of als u handmatige bootstrapping gebruikt:

angular.bootstrap(document, ['DemoApp'], {
    strictDi: true
});

Een keer binden

Angular staat bekend om zijn geweldige bidirectionele gegevensbinding. Standaard synchroniseert Angular continu waarden die zijn gebonden tussen model- en weergavecomponenten wanneer gegevens in het model of de weergavecomponent worden gewijzigd.

Dit brengt de kosten met zich mee dat het een beetje traag is als het teveel wordt gebruikt. Dit zal een grotere prestatiehit hebben:

Slechte prestaties: {{my.data}}

Voeg twee dubbele punten :: voor de variabelenaam toe om eenmalige binding te gebruiken. In dit geval wordt de waarde pas bijgewerkt zodra my.data is gedefinieerd. U wijst er nadrukkelijk op niet te letten op gegevenswijzigingen. Angular voert geen waardecontroles uit, waardoor er minder expressies worden geëvalueerd bij elke digestcyclus.

Goede prestatie voorbeelden met behulp van eenmalige binding

{{::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>

Opmerking: Dit verwijdert echter de bidirectionele gegevensbinding voor my.data , dus wanneer dit veld in uw toepassing verandert, wordt dit niet automatisch weerspiegeld in de weergave. Gebruik het dus alleen voor waarden die niet veranderen gedurende de levensduur van uw applicatie .

Toepassingsfuncties en filters

AngularJS heeft een digest-lus en al uw functies in een weergave en filters worden uitgevoerd telkens wanneer de digest-cyclus wordt uitgevoerd. De digest-lus wordt uitgevoerd wanneer het model wordt bijgewerkt en het kan uw app vertragen (filter kan meerdere keren worden geraakt, voordat de pagina wordt geladen).

U moet dit vermijden:

<div ng-controller="bigCalulations as calc">
  <p>{{calc.calculateMe()}}</p>
  <p>{{calc.data | heavyFilter}}</p>
</div>

Betere aanpak

<div ng-controller="bigCalulations as calc">
  <p>{{calc.preCalculatedValue}}</p>
  <p>{{calc.data | lightFilter}}</p>
</div>

Waar het controller-monster is:

.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
});

Watchers

Kijkers hebben nodig om een waarde te bekijken en te detecteren dat deze waarde is gewijzigd.

Na oproep $watch() of $watchCollection nieuwe watcher toevoegen aan interne watcher-collectie in huidige scope.

Wat is watcher?

Watcher is een eenvoudige functie, die bij elke digest-cyclus wordt aangeroepen en enige waarde retourneert. Angular controleert de geretourneerde waarde, als deze niet hetzelfde is als bij de vorige aanroep - een callback die in de tweede parameter is doorgegeven om $watch() of $watchCollection , wordt uitgevoerd.

(function() {
  angular.module("app", []).controller("ctrl", function($scope) {
    $scope.value = 10;
    $scope.$watch(
      function() { return $scope.value; },
      function() { console.log("value changed"); }
    );
  }
})();

Kijkers zijn prestatiemoordenaars. Hoe meer kijkers je hebt, hoe langer het duurt om een digest-lus te maken, hoe langzamer de gebruikersinterface. Als een kijker veranderingen detecteert, start deze de digest-lus (herberekening op alle schermen)

Er zijn drie manieren om handmatig te kijken naar variabele wijzigingen in Angular.

$watch() - let alleen op waardewijzigingen

$watchCollection() - let op wijzigingen in collectie (horloges meer dan regulier $ watch)

$watch(..., true) - Vermijd dit zoveel mogelijk, het zal "deep watch" uitvoeren en de uitvoering doden (horloges meer dan watchCollection)

Merk op dat als u variabelen in de weergave bindt, u nieuwe watchers maakt - gebruik {{::variable}} niet om watcher te maken, vooral in loops

Daarom moet u bijhouden hoeveel kijkers u gebruikt. U kunt de kijkers met dit script tellen (met dank aan @Words Like Jared - Hoe het totale aantal horloges op een pagina tellen?

(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);

})();

Als u geen eigen script wilt maken, is er een open source-hulpprogramma genaamd ng-stats dat een realtime grafiek gebruikt die is ingebed in de pagina om u inzicht te geven in het aantal horloges dat Angular beheert, evenals de frequentie en duur van digestcycli in de tijd. Het hulpprogramma biedt een algemene functie met de naam showAngularStats die u kunt oproepen om te configureren hoe u de grafiek wilt laten werken.

showAngularStats({
  "position": "topleft",
  "digestTimeThreshold": 16,
  "autoload": true,
  "logDigest": true,
  "logWatches": true
});

De bovenstaande voorbeeldcode geeft automatisch de volgende grafiek op de pagina weer ( interactieve demo ).

screenshot van ng-stats grafiek

ng-if vs ng-show

Deze functies lijken erg op elkaar. Het verschil is dat ng-if elementen uit de DOM verwijdert. Als er grote delen van de code niet worden weergegeven, is ng-if de beste keuze. ng-show verbergt alleen de elementen maar behoudt alle handlers.

ng-if

De ngIf-instructie verwijdert of maakt een gedeelte van de DOM-structuur opnieuw op basis van een expressie. Als de expressie die is toegewezen aan ngIf resulteert in een valse waarde, wordt het element verwijderd uit de DOM, anders wordt een kloon van het element opnieuw in de DOM geplaatst.

ng-voorstelling

De ngShow-instructie toont of verbergt het gegeven HTML-element op basis van de uitdrukking die aan het attribuut ngShow wordt gegeven. Het element wordt getoond of verborgen door de CSS-klasse ng-hide aan het element te verwijderen of toe te voegen.

Voorbeeld

<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>

Conclusie

Het hangt af van het type gebruik, maar vaak is de een geschikter dan de andere (bijv., Als 95% van de tijd het element niet nodig is, gebruik ng-if ; als u de zichtbaarheid van het DOM-element wilt wijzigen, gebruik dan ng-show ).

Gebruik bij twijfel ng-if en test!

Opmerking : ng-if maakt een nieuw geïsoleerd bereik, terwijl ng-show en ng-hide dat niet doen. Gebruik $parent.property als de eigenschap $parent.property er niet rechtstreeks toegankelijk is.

Debounce uw model

<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>

In het bovenstaande voorbeeld stellen we een debounce-waarde in van 1000 milliseconden, wat 1 seconde is. Dit is een aanzienlijke vertraging, maar zal voorkomen dat de invoer herhaaldelijk het ng-model verslaat met veel $digest cycli.

Door debounce te gebruiken op uw invoervelden en overal waar een directe update niet nodig is, kunt u de prestaties van uw Angular-apps behoorlijk verbeteren. U kunt niet alleen met tijd vertragen, maar u kunt ook vertragen wanneer de actie wordt geactiveerd. Als je je ng-model niet bij elke toetsaanslag wilt bijwerken, kun je ook bijwerken bij vervaging.

Luisteraars altijd afmelden voor andere scopes dan de huidige scope

U moet altijd andere scopes afmelden dan uw huidige scope, zoals hieronder weergegeven:

//always deregister these
$rootScope.$on(...);
$scope.$parent.$on(...);

U hoeft de listners niet uit te schrijven voor het huidige bereik, omdat Angular dit zou regelen:

//no need to deregister this
$scope.$on(...);

$rootScope.$on luisteraars blijft in het geheugen als u naar een andere controller navigeert. Dit veroorzaakt een geheugenlek als de controller buiten bereik valt.

doe niet

angular.module('app').controller('badExampleController', badExample);
badExample.$inject = ['$scope', '$rootScope'];

function badExample($scope, $rootScope) {
    $rootScope.$on('post:created', function postCreated(event, data) {});
}

Doen

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();
    });
}


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow