web-dev-qa-db-fra.com

Pourquoi le code "element.innerHTML + =" est-il incorrect?

On m'a dit de ne pas ajouter de contenu en utilisant element.innerHTML += ... comme ça:

var str = "<div>hello world</div>";
var Elm = document.getElementById("targetID");

Elm.innerHTML += str; //not a good idea?

Qu'est-ce qui ne va pas ?, Quelles autres alternatives ai-je?

44
ajax333221

Chaque fois que innerHTML est défini, le code HTML doit être analysé, un DOM construit et inséré dans le document. Cela prend du temps.

Par exemple, si Elm.innerHTML a des milliers de divs, tables, listes, images, etc., puis appelle .innerHTML += ... va amener l'analyseur à ré-analyser toutes ces choses à nouveau. Cela pourrait également casser les références aux éléments DOM déjà construits et provoquer un autre chaos. En réalité, tout ce que vous voulez faire, c'est ajouter un seul nouvel élément à la fin.

Il vaut mieux appeler simplement appendChild:

var newElement = document.createElement('div');
newElement.innerHTML = '<div>Hello World!</div>';
Elm.appendChild(newElement);​​​​​​​​​​​​​​​​

De cette façon, le contenu existant de Elm n'est plus analysé.

REMARQUE: Il est possible que [certains] navigateurs soient suffisamment intelligents pour optimiser le += et ne pas ré-analyser le contenu existant. Je n'ai pas recherché cela.

35
Mike Christensen

Oui, Elm.innerHTML += str; Est une très mauvaise idée.
Utilisez Elm.insertAdjacentHTML( 'beforeend', str ) comme alternative parfaite.

La réponse typique "le navigateur doit reconstruire DOM" ne rend pas vraiment justice à la question:

  1. Tout d'abord, le navigateur doit parcourir chaque élément sous Elm, chacune de leurs propriétés, tous leurs textes, commentaires et nœuds de processus, et leur échapper pour vous construire une chaîne.

  2. Ensuite, vous avez une longue chaîne à laquelle vous ajoutez. Cette étape est ok.

  3. Troisièmement, lorsque vous définissez innerHTML, le navigateur doit supprimer tous les éléments, propriétés et nœuds qu'il vient de traverser.

  4. Ensuite, il analyse la chaîne, construit à partir de tous les éléments, propriétés et nœuds qu'il vient de détruire, pour créer un nouveau fragment DOM qui est essentiellement identique.

  5. Enfin, il attache les nouveaux nœuds et le navigateur doit tout mettre en page. Cela peut être évitable (voir l'alternative ci-dessous), mais même si le ou les nœuds ajoutés nécessitent une disposition, les anciens nœuds auraient leurs propriétés de disposition mises en cache au lieu d'être recalculées à partir de la nouvelle.

  6. Mais ce n'est pas encore fait! Le navigateur doit également recycler les anciens nœuds en scannant les variables tous javascript.

Problèmes:

  • Certaines propriétés peuvent ne pas être reflétées par HTML, par exemple la valeur actuelle de <input> Sera perdue et réinitialisée à la valeur initiale dans le HTML.

  • Si vous avez des gestionnaires d'événements sur les anciens nœuds, ils seront détruits et vous devrez tous les rattacher.

  • Si votre code js fait référence à d'anciens nœuds, ils ne seront pas détruits mais seront à la place orphelins. Ils appartiennent au document mais ne sont plus dans l'arborescence DOM. Lorsque votre code y accède, rien ne peut se produire ou cela peut générer une erreur.

  • Les deux problèmes signifient qu'il n'est pas convivial avec les plugins js - les plugins peuvent attacher des gestionnaires ou se souvenir d'anciens nœuds et provoquer une fuite de mémoire.

  • Si vous prenez l'habitude de faire de la manipulation DOM avec innerHTML, vous pouvez accidentellement modifier les propriétés ou faire d'autres choses que vous ne vouliez pas.

  • Plus vous avez de nœuds, plus cela est inefficace, plus il y a de batterie pour rien.

En bref, il est inefficace, il est sujet aux erreurs, il est simplement paresseux et mal informé.


La meilleure alternative est Element.insertAdjacentHTML, que je n'ai pas vu d'autres réponses mentionner:

Elm.insertAdjacentHTML( 'beforeend', str )

Presque le même code, sans les problèmes de innerHTML. Aucune reconstruction, aucun gestionnaire perdu, aucune réinitialisation d'entrée, moins de fragmentation de la mémoire, pas de mauvaise habitude, pas de créations et d'affectations manuelles d'éléments.

Il vous permet d'injecter une chaîne html dans des éléments sur une seule ligne, y compris les propriétés, et vous permet même d'injecter des éléments composites et multiples. Sa vitesse est optimisée - dans le test de Mozilla, elle est 150 fois plus rapide.

Si quelqu'un vous dit qu'il ne s'agit pas d'un navigateur croisé, il est si utile qu'il soit HTML5 standard et disponible dans tous les navigateurs .

N'écrivez plus jamais Elm.innerHTML+=.

21
Sheepy

L'alternative est .createElement(), .textContent Et .appendChild(). Ajouter avec += N'est un problème que si vous traitez beaucoup de données.

Démo: http://jsfiddle.net/ThinkingStiff/v6WgG/

Scénario

var Elm = document.getElementById( 'targetID' ),
    div = document.createElement( 'div' );
div.textContent = 'goodbye world';
Elm.appendChild( div );

HTML

<div id="targetID">hello world</div>
4
ThinkingStiff

Si l'utilisateur a des versions plus anciennes de IE (ou peut-être des plus récentes aussi, n'ont pas essayé), innerHTML sur un td causera des problèmes. Les éléments du tableau dans IE sont en lecture seule, tsk tsk tsk.

1
Tim

Je viens d'apprendre à la dure pourquoi le innerHTML est mauvais, dans ce code ci-dessous lorsque vous définissez innerHTML chrome perd l'événement onclick jsFiddle

var blah = document.getElementById('blah');
var div = document.createElement('button');
div.style['background-color'] = 'black';
div.style.padding = '20px;';
div.style.innerHTML = 'a';
div.onclick = () => { alert('wtf');};

blah.appendChild(div);

// Uncomment this to make onclick stop working
blah.innerHTML += ' this is the culprit';

<div id="blah">
</div>
1
Adam Sparks

La réponse de Mike est probablement la meilleure, mais une autre considération est que vous avez affaire à des cordes. Et la concaténation de chaînes en JavaScript peut être très lente, en particulier dans certains navigateurs plus anciens. Si vous concaténez simplement de petits fragments de HTML, cela n'est probablement pas perceptible, mais si vous avez une partie importante de la page à laquelle vous ajoutez quelque chose à plusieurs reprises, vous pouvez très bien voir une pause notable dans le navigateur.

0
jjathman

Court

Si vous changez innerHTML += ... (Mise à jour du contenu) en innerHTML = ... (Régénération du contenu), vous obtiendrez un code très rapide . Il semble que la partie la plus lente de += Est la LECTURE du contenu DOM sous forme de chaîne (pas la transformation de chaîne en DOM)

L'inconvénient de l'utilisation de innerHTML est que vous perdez les anciens gestionnaires d'événements de contenu - cependant vous pouvez utiliser des arguments de balise pour omettre cela, par exemple <div onclick="yourfunc(event)"> qui est acceptable dans les petits projets

Longue

J'ai fait des tests de performances ICI sur Chrome, Firefox et Safari (2019 mai) (vous pouvez les exécuter sur votre machine mais soyez patient - cela prend ~ 5 min)

enter image description here

function up() {
  var container = document.createElement('div');
  container.id = 'container';
  container.innerHTML = "<p>Init <span>!!!</span></p>"
  document.body.appendChild(container);
}

function down() {
  container.remove()
}

up();

// innerHTML+=
container.innerHTML += "<p>Just first <span>text</span> here</p>";
container.innerHTML += "<p>Just second <span>text</span> here</p>";
container.innerHTML += "<p>Just third <span>text</span> here</p>";

down();up();

// innerHTML += str
var s='';
s += "<p>Just first <span>text</span> here</p>";
s += "<p>Just second <span>text</span> here</p>";
s += "<p>Just third <span>text</span> here</p>";
container.innerHTML += s;

down();up();

// innerHTML = innerHTML+str
var s=container.innerHTML+'';
s += "<p>Just first <span>text</span> here</p>";
s += "<p>Just second <span>text</span> here</p>";
s += "<p>Just third <span>text</span> here</p>";
container.innerHTML = s;

down();up();

// innerHTML = str
var s="<p>Init <span>!!!</span></p>";
s += "<p>Just first <span>text</span> here</p>";
s += "<p>Just second <span>text</span> here</p>";
s += "<p>Just third <span>text</span> here</p>";
container.innerHTML = s;

down();up();

// insertAdjacentHTML str
var s='';
s += "<p>Just first <span>text</span> here</p>";
s += "<p>Just second <span>text</span> here</p>";
s += "<p>Just third <span>text</span> here</p>";
container.insertAdjacentHTML("beforeend",s);

down();up();

// insertAdjacentHTML
container.insertAdjacentHTML("beforeend","<p>Just first <span>text</span> here</p>");
container.insertAdjacentHTML("beforeend","<p>Just second <span>text</span> here</p>");
container.insertAdjacentHTML("beforeend","<p>Just third <span>text</span> here</p>");

down();up();

// appendChild
var p1 = document.createElement('p');
p1.innerHTML = 'Just first <span>text</span> here';
var p2 = document.createElement('p');
p2.innerHTML = 'Just second <span>text</span> here';
var p3 = document.createElement('p');
p3.innerHTML = 'Just third <span>text</span> here';
container.appendChild(p1);
container.appendChild(p2);
container.appendChild(p3);
b {color: red}
<b>This snippet NOT test anythig - only presents code used in tests</b>

  • Pour tous les navigateurs, le innerHTML += Était la solution la plus lente.
  • Les solutions les plus rapides pour tous les navigateurs étaient innerHTML = str Et insertAdjacentHTML str (le plus rapide)
  • Si nous regardons de plus près le cas innerHTML = innerHTML +str Et comparons avec innerHTML = str Il semble que la partie la plus lente de innerHTML += Est [~ # ~] lecture [~ # ~] Contenu DOM sous forme de chaîne (pas de transformation de chaîne en DOM)
  • createElement/appendChild est une solution à vitesse moyenne
  • Si vous voulez changer l'arborescence DOM, générez la première chaîne entière (avec html) et mettez à jour/régénérez DOM uniquement [~ # ~] une fois [~ # ~]
0