AngularJS

At 60 FPS

Wassim Chegham

aka — Maneki Nekko

ECMAScript Jedi
Technical Lead

Vincent Ogloblinsky

 

Javascript addict
Technical Lead

RennesJS






État de l'art

fonctionnement du navigateur

Processus d'affichage


Attention au repaint/reflow !

Un mot sur JavaScript...



Outillage et diagnostique

Comment et avec quoi debugger correctement

Chrome devtools

Connue de tous les développeurs web aujourd'hui.

Les onglets utiles pour du débuggage de performances sont :

  • Timeline : enregistrement et analyse du rendu d'une page web, avec filtrage possible suivant l'étape : Loading, Scripting, Rendering, and Painting


  • Profiles : profilage des performances JS






Liens

https://developer.chrome.com/devtools/docs/timeline?hl=FR

http://addyosmani.com/blog/performance-optimisation-with-timeline-profiles/

Batarang

Extension pour Chrome Devtools dédiée au débuggage d'applications AngularJS

https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk


Propose plusieurs onglets permettant :

  • analyse des modèles
  • analyse des watchers
  • graphe des dépendances des modules, services, etc
  • options de visualisations des scopes, bindings et applications dans la page HTML

Ajoute également un onglet 'AngularJS Properties' dans l'onglet principal 'Elements' de Devtools

Angular inspector bookmarklet


Marque page à exécuter sur une application AngularJS qui propose à peut près la même chose que Batarang :

  • visualisations des scopes, bindings

mais aussi :

  • watches en surcouche dans la page !
  • watches par scope avec un nombre en haut à droite par rapport au nombre total de watches de l'application

https://github.com/maxdow/Angular-Inspector-Bookmarklet


Optimiser les performances de votre ng-application




SPA classique

Mise en cache du DOM

Exemple avec jQuery

  • avant :
$('div span .cls').each(...);
//...
$('div span').animate(...);
  • après :
var spans = $('.div').find('span');
spans.filter('.cls').each(...);
//...
spans.animate(...);

Manipulation du DOM

Exemple en Vanilla

  • avant :
doc.innerHTML = '<ul>';
arr.forEach(function(prd){
    doc.innerHTML += '<li>'+prd+'<li>';
});
doc.innerHTML += '</ul>';
  • après :
var html = '<ul>';
arr.forEach(function(prd){
    html += '<li>'+prd+'<li>';
});
doc.innerHTML = html + '</ul>';

Sélecteurs CSS

Les sélecteurs universels sont à proscrire

* { color: red; }
[type="text"] { color: red; }
input[type="text"] { color: red; }

Repaint et reflow

Éviter le reflow et minimiser les repaint

  • Redimensionner la fenêtre
  • Changer la police
  • Calculer du offsetWidth et offsetHeight
  • Modifier des attributs de l'objet style
  • ...

Gestion correcte des évènements

  • Enregistrement/désenregistrement systématique
el.addEventListener('click', fn, false);
el.removeEventListener('click', fn, false);
  • Délégation d'évenements
ul.addEventListener('click', function(e){
  if( e.target.nodeName === 'LI'){
    //...
  }
}, false);
  • Attention au mousemove/touchmove, onscroll

Quelques ressources




SPA AngularJS

dirty checking vs. Object.observe

Le Dirty checking cherche qui a changé lors d'une action x ou y.

La future spécification ECMAScript avec l'ajout de la méthode Observe à la classe Object bouleverse ce mécanisme. Elle permet de recevoir un évènement de changement d'un objet.

dirty checking vs. Object.observe

var beingWatched = {};
// Define callback function to get notified on changes
function somethingChanged(changes) {
    // do something
}
Object.observe(beingWatched, somethingChanged);

Le résultat est très important, (20x à 40x plus rapide par ex)

Prévu pour la version 2, et disponible actuellement dans une librairie séparée : watchtower.js écrite en ES6

Object.observe disponible dans Chrome v25 depuis le 21/05/2014 !

Liens :

http://www.html5rocks.com/en/tutorials/es7/observe/ http://updates.html5rocks.com/2012/11/Respond-to-change-with-Object-observe http://addyosmani.com/blog/the-future-of-data-binding-is-object-observe/

Surveillez votre

$watch

scope.$watch(watchExpression, listener, true/false);
  • watchExpression sera exécutée plusieurs fois
  • Deux modes de comparaison
    • FALSE : (par référence) rapide
    • TRUE : (récursif) très très lent !!

Préférez

$watchCollection

scope.$watchCollection(obj, listener);
  • Ajouté en 1.2, utilisé par ng-repeat
  • Un niveau de profondeur
  • Alternative au $watch récursif





$watch vs. $watchCollection

demo

$apply & $digest & $$postdigest

  • $apply appelle les watchers dans la chaîne entière du scope + $digest sur la fin...

  • $digest appelle les watchers dans le scope courant et ses enfants

  • $$postDigest appelle un callback défini une fois le cycle $digest terminé. Il permet par exemple de MAJ le dom après un dirty checking

$$ === private pour Angular

$scope.$$postDigest(function(){
  console.log("post Digest");
});

On préfére alors la méthode $timeout,

$timeout(function(){
 console.log("post Digest with $timeout");
},0,false);

$eval, $parse, $interpolate

  • Attention : $eval appelle $parse
for (var i=0; i<10E100; i+=1){
  $scope.$eval('expression');
}
  • Il vaut mieux appeler $parse une fois
var parsedExp = $parse('expression');
for (var i=0; i<10E10; i+=1){
  parsedExp($scope);
}
  • $parse est beaucoup plus rapide que $interpolate

(pour les expressions simples)

directives : compile, link

  • Pour les directives déclarées dans ng-repeat
    • compile est appelée qu'une fois
    • link et le constructeur sont appelés à chaque itération
  • compile est votre ami




directives : transclusion

$digest limité au scope de la directive

Exemple

  • avant :
app.directive(function(){
  return {
    link: function(s,e,a){
      s.foo = s.$eval(a.foo);
      s.$watch(a.list, function(list){}, true);
    }
  }
});
  • après :
app.directive(function($parse){
  return {
    transclude: true,
    compile: function(e,a){
      var fooExp = $parse(a.foo), listExp = $parse(a.list);
      return function link(s,e){
        s.foo = fooExp(s);
        s.$watchCollection(listExp, function(list){});
      }
    }
  }
});

ng-repeat : track by $index, pagination

Par défaut, ng-repeat crée un noeud DOM par élément, et détruit le noeud quand l'item est supprimé.

Avec track by $index, la directive va réutiliser ces noeuds DOM.

<div ng-repeat="item in array">
  Hello, i am {{item}}.
<div>
<div ng-repeat="item in array track by $index">
  Hello, i am {{item}}.
<div>

-> demo

-> demo tracked

ng-if vs ng-show

ng-show cache les éléments en CSS ( display:none )

  • les bindings sont toujours présent

ng-if va plus loin, et ne les crée même pas dans le DOM

  • moins de bindings
  • crée un scope sur l'enfant


-> demo ng-show

-> demo ng-if

Filtres

Ils sont executés à chaque fin de cycle $digest. Ils doivent donc être très rapides.

A n'appliquer que si nécessaire dans une liste par exemple. Ajouter plutôt le resultat du filtre dans la liste avant son affichage.


-> demo ng-filter

-> demo ng-filter optimized

Mono-binding / once

Lors de l'utilisation de {{ }}, Angular crée un watch interne pour démarrer le processus de data-binding.

'Très' utile si la donnée change au cours du temps, mais si la donnée est en lecture seule, ce n'est plus utile.

But : allègement des watchers, le cycle de $digest sera plus cours également.

Solution : débrancher le watch une fois la donnée affichée.


https://github.com/tadeuszwojcik/angular-once

Mono-binding / once

Data-binded

<ul>
    <li ng-repeat="user in users">
      <a ng-href="{{ user.profileUrl }}">{{ user.name }}</a>
    </li>
</ul>

Sur une liste de 100 élements : 101 watchers

-> demo without-once

Onced

<ul>
    <li ng-repeat="user in users">
      <a once-href="user.profileUrl" once-text="user.name"></a>
    </li>
</ul>

Sur une liste de 100 élements : 1 watchers

-> demo with-once



One more thing

Angular 2.0

  • evergreen browsers
  • ES6+
  • Object.observe
  • modularity (lazy-loading)
  • ...

http://blog.angularjs.org/2014/03/angular-20.html

Angular + ReactJS

MVC

M (react) C

Affichage sans React (1200ms)

 

Affichage avec React (320ms) 


Plus d'informations

http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/



Conclusion





/merci

@vogloblinsky

@manekinekko

AngularJS at 60fps

By Wassim Chegham

AngularJS at 60fps

Une présentation sur les performances des applications SPA en générales et AngularJS en particulier. Cette conférence a été présentée lors du BreizhCamp 2014, par Vincent Obloblinsky et moi même. Voici les sources des slides ainsi que les demos présentées : https://github.com/vogloblinsky/angularjsat60fps

  • 4,539