aka — Maneki Nekko
ECMAScript Jedi
Technical Lead
Attention au repaint/reflow !
Connue de tous les développeurs web aujourd'hui.
Les onglets utiles pour du débuggage de performances sont :
Liens
https://developer.chrome.com/devtools/docs/timeline?hl=FR
http://addyosmani.com/blog/performance-optimisation-with-timeline-profiles/
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 :
Ajoute également un onglet 'AngularJS Properties' dans l'onglet principal 'Elements' de Devtools
Marque page à exécuter sur une application AngularJS qui propose à peut près la même chose que Batarang :
mais aussi :
$('div span .cls').each(...);
//...
$('div span').animate(...);
var spans = $('.div').find('span');
spans.filter('.cls').each(...);
//...
spans.animate(...);
doc.innerHTML = '<ul>';
arr.forEach(function(prd){
doc.innerHTML += '<li>'+prd+'<li>';
});
doc.innerHTML += '</ul>';
var html = '<ul>';
arr.forEach(function(prd){
html += '<li>'+prd+'<li>';
});
doc.innerHTML = html + '</ul>';
Les sélecteurs universels sont à proscrire
* { color: red; }
[type="text"] { color: red; }
input[type="text"] { color: red; }
Éviter le reflow et minimiser les repaint
offsetWidth
et offsetHeight
style
el.addEventListener('click', fn, false);
el.removeEventListener('click', fn, false);
ul.addEventListener('click', function(e){
if( e.target.nodeName === 'LI'){
//...
}
}, false);
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.
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/
scope.$watch(watchExpression, listener, true/false);
watchExpression
sera exécutée plusieurs foisscope.$watchCollection(obj, listener);
ng-repeat
$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);
for (var i=0; i<10E100; i+=1){
$scope.$eval('expression');
}
var parsedExp = $parse('expression');
for (var i=0; i<10E10; i+=1){
parsedExp($scope);
}
(pour les expressions simples)
ng-repeat
compile
est appelée qu'une foislink
et le constructeur sont appelés à chaque itérationcompile
est votre ami$digest limité au scope de la directive
app.directive(function(){
return {
link: function(s,e,a){
s.foo = s.$eval(a.foo);
s.$watch(a.list, function(list){}, true);
}
}
});
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){});
}
}
}
});
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>
ng-show cache les éléments en CSS ( display:none )
ng-if va plus loin, et ne les crée même pas dans le DOM
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.
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.
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
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
Affichage sans React (1200ms)
Affichage avec React (320ms)
Plus d'informations
http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/