Nous savons que DOM est une interface de programmation d'application pour exploiter des documents XML et HTML, et qu'il est très coûteux d'utiliser des scripts pour faire fonctionner DOM. Il existe une métaphore appropriée. Imaginez que DOM et JavaScript (ici ECMScript) soient chacun une île, et qu'ils soient reliés par un pont à péage. Chaque fois qu'ECMAScript accède au DOM, il doit passer par ce pont et payer un « péage de pont ». Plus vous accédez au DOM plusieurs fois, plus le coût est élevé. Par conséquent, l’approche recommandée consiste à traverser le moins de ponts possible et à essayer de rester sur l’îlot ECMAScript. Il nous est impossible de ne pas utiliser l'interface DOM, alors comment pouvons-nous améliorer l'efficacité du programme ?
1. Accès et modification du DOM
Accéder aux éléments du DOM est coûteux (vous connaissez le "péage"), et modifier des éléments est encore plus coûteux car cela oblige le navigateur à recalculer les changements géométriques de la page (redistribution et redessinage).
Bien sûr, le pire des cas est d'accéder ou de modifier des éléments dans une boucle, regardez les deux morceaux de code suivants :
var times = 15000; // code1 console.time(1); for(var i = 0; i < times; i++) { document.getElementById('myDiv1').innerHTML += 'a'; } console.timeEnd(1); // code2 console.time(2); var str = ''; for(var i = 0; i < times; i++) { str += 'a'; } document.getElementById('myDiv2').innerHTML = str; console.timeEnd(2);
Du coup, la première durée de fonctionnement était mille fois plus longue que la deuxième fois ! (version chromée 44.0.2403.130m)
1: 2846.700ms 2: 1.046ms
Le problème avec le premier morceau de code est qu'à chaque itération de boucle, l'élément est accédé deux fois : une fois pour lire la valeur de innerHTML, et une autre fois pour la réécrire, c'est-à-dire à chaque fois que la boucle traverse le pont ( le re-Aviron et le redessin seront expliqués dans le prochain article) ! Les résultats montrent clairement que plus le DOM est accédé, plus le code s'exécute lentement. Par conséquent, le nombre d'accès DOM pouvant être réduits est réduit autant que possible et le traitement est laissé autant que possible du côté ECMAScript.
2. Collection HTML et traversée du DOM
Un autre point consommateur d'énergie dans l'exploitation du DOM est la traversée du DOM. Généralement, nous collecterons une collection de HTML, par exemple en utilisant getElementsByTagName(), ou document.links, etc. Je pense que tout le monde est familier avec cela. Le résultat de la collection est une collection de type tableau qui existe dans un « état actif » en temps réel, ce qui signifie qu'elle se met automatiquement à jour lorsque l'objet document sous-jacent est mis à jour. Comment le dire ? C'est très simple d'offrir une châtaigne :
<body> <ul id='fruit'> <li> apple </li> <li> orange </li> <li> banana </li> </ul> </body> <script type="text/javascript"> var lis = document.getElementsByTagName('li'); var peach = document.createElement('li'); peach.innerHTML = 'peach'; document.getElementById('fruit').appendChild(peach); console.log(lis.length); // 4 </script>
Et c’est de là que vient l’inefficacité ! C'est très simple. Tout comme l'opération d'optimisation du tableau, la mise en cache de la variable de longueur est correcte (la lecture de la longueur d'une collection est beaucoup plus lente que la lecture de la longueur d'un tableau ordinaire, car elle doit être interrogée à chaque fois) :
console.time(0); var lis0 = document.getElementsByTagName('li'); var str0 = ''; for(var i = 0; i < lis0.length; i++) { str0 += lis0[i].innerHTML; } console.timeEnd(0); console.time(1); var lis1 = document.getElementsByTagName('li'); var str1 = ''; for(var i = 0, len = lis1.length; i < len; i++) { str1 += lis1[i].innerHTML; } console.timeEnd(1);
Voyons dans quelle mesure l’amélioration des performances peut être obtenue ?
0: 0.974ms 1: 0.664ms
Lorsque la longueur de la collection est grande (la démo est de 1000), l'amélioration des performances est toujours évidente.
"JavaScript haute performance" propose une autre stratégie d'optimisation, qui stipule : "Parce que parcourir un tableau est plus rapide que parcourir une collection, si vous copiez d'abord les éléments de la collection dans le tableau, l'accès à ses propriétés sera plus rapide." n'a pas très bien révélé ce modèle, alors ne vous en souciez pas. Le code du test est le suivant : (Si vous avez des questions, n'hésitez pas à en discuter avec moi)
console.time(1); var lis1 = document.getElementsByTagName('li'); var str1 = ''; for(var i = 0, len = lis1.length; i < len; i++) { str1 += lis1[i].innerHTML; } console.timeEnd(1); console.time(2); var lis2 = document.getElementsByTagName('li'); var a = []; for(var i = 0, len = lis2.length; i < len; i++) a[i] = lis2[i]; var str2 = ''; for(var i = 0, len = a.length; i < len; i++) { str2 += a[i].innerHTML; } console.timeEnd(2);
À la fin de cette section, nous introduisons deux méthodes DOM natives, querySelector() et querySelectorAll(), je pense que tout le monde les connaît. La première renvoie un tableau (notez que leurs valeurs de retour ne changent pas dynamiquement). comme les collections HTML), et cette dernière ou renvoie le premier élément correspondant. Eh bien, il ne fonctionne pas toujours mieux que le parcours de collection HTML du premier.
console.time(1); var lis1 = document.getElementsByTagName('li'); console.timeEnd(1); console.time(2); var lis2 = document.querySelectorAll('li'); console.timeEnd(2); // 1: 0.038ms // 2: 3.957ms
Mais comme il s'agit d'une méthode de sélection de type CSS, elle est plus efficace et plus pratique lors des sélections combinées. Par exemple, effectuez la requête combinée suivante :
var elements = document.querySelectorAll('#menu a'); var elements = document.querySelectorAll('div.warning, div.notice');
Ce qui précède concerne la programmation DOM JavaScript hautes performances. J'espère que vous pourrez le comprendre et que cela vous sera utile pour votre apprentissage.