Vorteile von createElement gegenüber innerHTML?

79

Was sind in der Praxis die Vorteile von createElement gegenüber innerHTML? Ich frage, weil ich davon überzeugt bin, dass die Verwendung von innerHTML in Bezug auf Leistung und Lesbarkeit / Wartbarkeit von Code effizienter ist, aber meine Teamkollegen haben sich entschieden, createElement als Codierungsansatz zu verwenden. Ich möchte nur verstehen, wie createElement effizienter sein kann.

oninea
quelle

Antworten:

77

Neben der Sicherheit gibt es neben der Sicherheit mehrere Vorteile, die Sie verwenden müssen, createElementanstatt sie zu modifizieren innerHTML( anstatt sie einfach wegzuwerfen und zu ersetzen), wie Pekka bereits erwähnt hat:

Behält vorhandene Verweise auf DOM-Elemente beim Anhängen von Elementen bei

Wenn Sie an anhängen (oder auf andere Weise ändern) innerHTML, müssen alle DOM-Knoten in diesem Element neu analysiert und neu erstellt werden. Wenn Sie Verweise auf Knoten gespeichert haben, sind diese im Wesentlichen nutzlos, da sie nicht mehr angezeigt werden.

Erhält Ereignishandler, die an DOM-Elemente angehängt sind

Dies ist wirklich nur ein Sonderfall (obwohl häufig) des letzten. Durch die Einstellung innerHTMLwerden Ereignishandler nicht automatisch wieder mit den neu erstellten Elementen verknüpft. Sie müssten sie daher selbst verfolgen und manuell hinzufügen. Durch die Ereignisdelegierung kann dieses Problem in einigen Fällen behoben werden.

Könnte in einigen Fällen einfacher / schneller sein

Wenn Sie viele Ergänzungen innerHTMLvornehmen, möchten Sie auf keinen Fall weiter zurücksetzen, da das wiederholte erneute Parsen und Erstellen von Elementen langsamer ist, obwohl dies für einfache Änderungen schneller ist. Der Weg, dies zu umgehen, besteht darin, den HTML-Code in einer Zeichenfolge aufzubauen und innerHTMLeinmal festzulegen, wenn Sie fertig sind. Je nach Situation kann die Zeichenfolgenmanipulation langsamer sein als nur das Erstellen und Anhängen von Elementen.

Darüber hinaus kann der Code zur Manipulation von Zeichenfolgen komplizierter sein (insbesondere, wenn Sie möchten, dass er sicher ist).

Hier ist eine Funktion, die ich manchmal benutze, um sie bequemer zu machen createElement.

function isArray(a) {
    return Object.prototype.toString.call(a) === "[object Array]";
}

function make(desc) {
    if (!isArray(desc)) {
        return make.call(this, Array.prototype.slice.call(arguments));
    }

    var name = desc[0];
    var attributes = desc[1];

    var el = document.createElement(name);

    var start = 1;
    if (typeof attributes === "object" && attributes !== null && !isArray(attributes)) {
        for (var attr in attributes) {
            el[attr] = attributes[attr];
        }
        start = 2;
    }

    for (var i = start; i < desc.length; i++) {
        if (isArray(desc[i])) {
            el.appendChild(make(desc[i]));
        }
        else {
            el.appendChild(document.createTextNode(desc[i]));
        }
    }

    return el;
}

Wenn Sie es so nennen:

make(["p", "Here is a ", ["a", { href:"http://www.google.com/" }, "link"], "."]);

Sie erhalten das Äquivalent dieses HTML:

<p>Here is a <a href="http://www.google.com/">link</a>.</p>
Matthew Crumley
quelle
5
Es werden auch Geschwindigkeitsvorteile gezeigt, wenn ein DOM-Baumfragment isoliert erstellt und am Ende einmal an das echte DOM angehängt wird.
staticsan
@staticsan, das ist ein guter Punkt (und eine andere Sache, die die makeFunktion ein wenig einfacher machen kann).
Matthew Crumley
Das isolierte Erstellen eines DOM-Fragments beschleunigt das Einfügen auf jeden Fall. In Bezug auf innerHTML kann / sollte der gleiche Ansatz auch verwendet werden. Normalerweise schiebe ich meine HTML-Zeichenfolgen in ein Array und verbinde die Elemente mithilfe der integrierten Array-Verknüpfungsfunktion (meiner Erfahrung nach am schnellsten). Hänge ich sie dann an das DOM an.
Oninea
@Matthew Crumley: IYO, würden Sie denken, dass die oben erwähnten Vorteile (die alle bewertet werden) die Vorteile der Verwendung von innerHTML im praktischen Gebrauch überwiegen? Um genauer zu sein, nehmen wir an, Sie schreiben Code, um eine Tabelle dynamisch zu erstellen (eine ziemlich häufige Codierungsaufgabe). Würden Sie für beide Ansätze createElement- oder innerHTML-Gebäudefragmente verwenden.
Oninea
1
Ich vermeide generell innerHTMLwegen seiner Nachteile. Bei komplexen Markups (wie dem Erstellen einer Tabelle) schreibe ich normalerweise Funktionen, um jeden Teil des Markups zu generieren. Zum Beispiel hätte ich eine Funktion, die traus den Daten für jede Zeile ein generiert . Dann könnte ich eine andere Funktion haben, die die Zeilen zu einer Tabelle kombiniert. Jede Funktion kann so einfach sein wie das Aufrufen makemit den entsprechenden Argumenten. Sollte die Leistung jemals zu einem Problem werden, kann ich die Funktionen ändern, um HTML-Zeichenfolgen zurückzugeben.
Matthew Crumley
14

Obwohl innerHTMLes schneller sein kann, stimme ich nicht zu, dass es in Bezug auf Lesbarkeit oder Wartung besser ist. Es kann kürzer sein, alles in eine Zeichenfolge zu setzen, aber kürzerer Code ist nicht immer notwendigerweise wartbarer.

Die Verkettung von Zeichenfolgen lässt sich einfach nicht skalieren, wenn dynamische DOM-Elemente als Pluszeichen erstellt werden müssen und das Öffnen und Schließen von Anführungszeichen schwierig zu verfolgen ist. Betrachten Sie diese Beispiele:

Das resultierende Element ist ein Div mit zwei inneren Bereichen, deren Inhalt dynamisch ist. Einer der Klassennamen (Krieger) innerhalb der ersten Spanne ist ebenfalls dynamisch.

<div>
    <span class="person warrior">John Doe</span>
    <span class="time">30th May, 2010</span>
</div>

Angenommen, die folgenden Variablen sind bereits definiert:

var personClass = 'warrior';
var personName = 'John Doe';
var date = '30th May, 2010';

Wenn wir nur innerHTML verwenden und alles zu einer einzigen Zeichenfolge zusammenfügen, erhalten wir:

someElement.innerHTML = "<div><span class='person " + personClass + "'>" + personName + "</span><span class='time'>" + date + "</span></div>";

Das obige Durcheinander kann durch Ersetzen von Saiten behoben werden, um zu vermeiden, dass Saiten jedes Mal geöffnet und geschlossen werden. Selbst für einfache Textersetzungen bevorzuge ich die Verwendung replaceanstelle der Verkettung von Zeichenfolgen.

Dies ist eine einfache Funktion, die ein Objekt aus Schlüsseln und Ersetzungswerten nimmt und diese in der Zeichenfolge ersetzt. Es wird davon ausgegangen, dass den Schlüsseln ein Präfix vorangestellt ist, $um anzuzeigen, dass es sich um einen speziellen Wert handelt. Es werden keine Flucht- oder Kantenfälle ausgeführt, $die im Wiederbeschaffungswert usw. angegeben sind.

function replaceAll(string, map) {
    for(key in map) {
        string = string.replace("$" + key, map[key]);
    }
    return string;
}

var string = '<div><span class="person $type">$name</span><span class="time">$date</span></div>';
var html = replaceAll(string, {
    type: personClass,
    name: personName,
    date: date
});
someElement.innerHTML = html;

Dies kann verbessert werden, indem die Attribute, der Text usw. beim Erstellen des Objekts getrennt werden, um eine programmgesteuerte Kontrolle über die Elementkonstruktion zu erhalten. Mit MooTools können wir beispielsweise Objekteigenschaften als Karte übergeben. Dies ist sicherlich besser zu warten, und ich würde auch besser lesbar argumentieren. jQuery 1.4 verwendet eine ähnliche Syntax, um eine Zuordnung zum Initialisieren von DOM-Objekten zu übergeben.

var div = new Element('div');

var person = new Element('span', {
    'class': 'person ' + personClass,
    'text': personName
});

var when =  new Element('span', {
    'class': 'time',
    'text': date
});

div.adopt([person, when]);

Ich würde den reinen DOM-Ansatz unten nicht als besser lesbar bezeichnen als den oben genannten, aber er ist sicherlich besser zu warten, da wir das Öffnen / Schließen von Anführungszeichen und zahlreiche Pluszeichen nicht im Auge behalten müssen.

var div = document.createElement('div');

var person = document.createElement('span');
person.className = 'person ' + personClass;
person.appendChild(document.createTextNode(personName));

var when = document.createElement('span');
​when.className = 'date​​​​​​';
when.appendChild(document.createTextNode(date));

​div.appendChild(person);
div.appendChild(when);

Die am besten lesbare Version würde sich höchstwahrscheinlich aus der Verwendung einer Art JavaScript-Vorlage ergeben .

<div id="personTemplate">
    <span class="person <%= type %>"><%= name %></span>
    <span class="time"><%= date %></span>
</div>

var div = $("#personTemplate").create({
    name: personName,
    type: personClass,
    date: date
});
Anurag
quelle
@ Anurag: Für Ihr erstes Beispiel zu innerHTML würde ich es eher so schreiben. Es ist ein bisschen länger, ich weiß, aber persönlich ist es für mich lesbar, sogar die Struktur des Fragments bleibt erhalten. Irgendeine Meinung dazu? array.push ("<div>"); array.push ("<span class = '", person + personClass, "'>", personName, "</ span>"); array.push ("<span class = '", time, "'>", date, </ span> "); array.push (" </ div> "); someElement.innerHTML = array.join (" " )
oninea
Setzen Sie den Code in Backticks var a = hello, aber ich kann Ihren Code herausfinden. Das sieht besser lesbar aus als eine einzelne verkettete Zeichenfolge.
Anurag
Der Umgang mit Tag / Attribut-Öffnungen und -Verschlüssen bleibt auch dann ärgerlich, wenn er in verschiedene Schritte unterteilt ist. Stattdessen sind String-Ersetzungen viel sauberer und fehlerfreier. Ich habe oben in meiner Antwort ein Beispiel hinzugefügt. Möglicherweise fehlt die für den IE erforderliche Mikrooptimierung für die Zeichenfolgenkonstruktion, aber Sie sollten Ihren Code für einen Browser nicht komplizieren, es sei denn, Sie müssen dies wirklich tun.
Anurag
1
innerHTMLschneller ? Nicht nach jsperf.com/innerhtml-vs-createelement-and-appendchild
jpillora
Ich denke nicht, dass dieses Beispiel schlüssig genug ist, um etwas zu beweisen. Sie müssen DOMs unterschiedlicher Größe und Komplexität (Tiefe) mit innerHTML und manueller Konstruktion erstellen. Wenn die Ergebnisse konsistent sind, würde dies deutlich zeigen, dass innerHTML langsamer ist. Außerdem ist dieser Test stark verzerrt, da die Rendering-Engine gezwungen ist, das gesamte DOM für das Body-Tag ungültig zu machen und es zu rekonstruieren. Während im anderen Beispiel nur ein neuer Knoten an das DOM angehängt wird und die anderen Teile des Body-Tags nicht berührt werden.
Anurag
14

User Bobince bringt eine Reihe von Nachteilen sehr, sehr gut in seine Kritik an jQuery ein .

... Außerdem können Sie ein Div erstellen, indem Sie $ ('' + message + '') sagen, anstatt mit document.createElement ('div') und Textknoten herumspielen zu müssen. Hurra! Nur ... warte. Sie sind diesem HTML-Code nicht entkommen und haben wahrscheinlich gerade eine Sicherheitslücke für Cross-Site-Scripting erstellt, diesmal nur auf der Clientseite. Und nachdem Sie so lange damit verbracht hatten, Ihr PHP zu bereinigen, um htmlspecialchars auch auf der Serverseite zu verwenden. Schade. Na ja, niemand kümmert sich wirklich um Korrektheit oder Sicherheit, oder?

jQuery ist nicht ganz schuld daran. Immerhin gibt es die innerHTML-Eigenschaft schon seit Jahren und sie hat sich bereits als beliebter als DOM erwiesen. Aber die Bibliothek fördert sicherlich diesen Codierungsstil.

Was die Leistung betrifft : InnerHTML wird definitiv langsamer sein, da es analysiert und intern in DOM-Elemente konvertiert werden muss (möglicherweise mithilfe der createElementMethode).

InnerHTML ist in allen Browsern gemäß dem von @Pointy bereitgestellten Quirksmode-Benchmark schneller.

Wie für die Lesbarkeit und Benutzerfreundlichkeit, finden Sie mir die Wahl innerHTMLüber createElementin den meisten Projekten an jedem Tag der Woche. Aber wie Sie sehen, gibt es viele Punkte, für die gesprochen wird createElement.

Pekka
quelle
1
Uhh @Pekka bist du dir wirklich sicher innerHTML, langsamer zu sein? Ich weiß, dass das lange Zeit definitiv falsch war. innerHTMLTatsächlich wurde die Verwendung in Frameworks gerade wegen des dramatischen Leistungsvorteils in einigen Browsern populär (raten Sie mal).
Pointy
@Pointy Interessant. Ich habe keine Benchmarks , aber der gesunde Menschenverstand sagt mir innerHTML- hat , langsamer sein: Es muss analysiert werden, validiert und verwandelte sich in DOM - Elemente, etwas , das createElementim ersten Schritt tut. Wenn Sie jedoch Benchmarks kennen, die etwas anderes aussagen, stehe ich gerne korrigiert da.
Pekka
3
Nun , die Quirksmode Benchmark ist ein wenig alt: quirksmode.org/dom/innerhtml.html
Zipfel
@Pointy noch: Wow! Geht total gegen das, was ich gedacht hätte. Prost, werde meine Antwort aktualisieren.
Pekka
1
Vielen Dank für den Einblick Pekka und Pointy. Dies bestätigt meine Ansicht, dass innerHTML schneller ist (zusätzlich zur Codierungserfahrung). Die Verwendung von HTML-Zeichenfolgen in einem Array bietet außerdem einen zusätzlichen Leistungsgewinn und eine zusätzliche Lesbarkeit. Ich möchte wissen, ob createElement Punkte für die Verwendung hat.
Oninea
8

Sie sollten createElement verwenden, wenn Sie Referenzen in Ihrem Code behalten möchten. InnerHTML kann manchmal einen Fehler verursachen, der schwer zu erkennen ist.

HTML Quelltext:

<p id="parent">sample <span id='test'>text</span> about anything</p>

JS-Code:

var test = document.getElementById("test");

test.style.color = "red"; //1 - it works

document.getElementById("parent").innerHTML += "whatever";

test.style.color = "green"; //2 - oooops

1) Sie können die Farbe ändern

2) Sie können die Farbe oder was auch immer nicht mehr ändern, da Sie in der obigen Zeile etwas zu innerHTML hinzugefügt haben und alles neu erstellt wurde und Sie Zugriff auf etwas haben, das nicht mehr existiert. Um es zu ändern, müssen Sie erneut GetElementById erhalten .

Sie müssen sich daran erinnern, dass dies auch alle Ereignisse betrifft. Sie müssen Ereignisse erneut anwenden.

InnerHTML ist großartig, weil es schneller und meistens einfacher zu lesen ist, aber Sie müssen vorsichtig sein und es mit Vorsicht verwenden. Wenn Sie wissen, was Sie tun, werden Sie in Ordnung sein.

Morfidon
quelle
0

Vorlagenliterale (Vorlagenzeichenfolgen) sind eine weitere Option.

const container = document.getElementById("container");

const item_value = "some Value";

const item = `<div>${item_value}</div>`

container.innerHTML = item;
CloudBranch
quelle
Achten Sie bei dieser Methode auf die HTML-Injection.
Sean