getElementsByTagName () entspricht textNodes


79

Gibt es eine Möglichkeit, die Sammlung aller textNodeObjekte in einem Dokument abzurufen?

getElementsByTagName()funktioniert gut für Elemente, aber textNodes sind keine Elemente.

Update: Mir ist klar, dass dies durch Gehen im DOM erreicht werden kann - wie viele unten vorschlagen. Ich weiß, wie man eine DOM-Walker-Funktion schreibt, die jeden Knoten im Dokument betrachtet. Ich hatte gehofft, dass es einen browser-nativen Weg gibt, dies zu tun. Immerhin ist es ein bisschen seltsam, dass ich alle <input>s mit einem einzigen eingebauten Anruf bekommen kann, aber nicht alle textNodes.

Antworten:


116

Update :

Ich habe einige grundlegende Leistungstests für jede dieser 6 Methoden über 1000 Läufe skizziert. getElementsByTagNameist das schnellste, aber es macht einen halbherzigen Job, da es nicht alle Elemente auswählt, sondern nur einen bestimmten Tag-Typ (glaube ich p) und blind davon ausgeht, dass sein erstes Kind ein Textelement ist. Es mag wenig fehlerhaft sein, aber es dient zu Demonstrationszwecken und zum Vergleich seiner Leistung mit TreeWalker. Führen Sie die Tests selbst auf jsfiddle durch , um die Ergebnisse zu sehen.

  1. Verwenden eines TreeWalker
  2. Benutzerdefinierte iterative Durchquerung
  3. Benutzerdefinierte rekursive Durchquerung
  4. Xpath-Abfrage
  5. querySelectorAll
  6. getElementsByTagName

Nehmen wir für einen Moment an, dass es eine Methode gibt, mit der Sie alle TextKnoten nativ abrufen können. Sie müssten immer noch jeden resultierenden Textknoten durchlaufen und aufrufen node.nodeValue, um den tatsächlichen Text zu erhalten, wie Sie es mit jedem DOM-Knoten tun würden. Das Problem der Leistung besteht also nicht darin, Textknoten zu durchlaufen, sondern alle Knoten, die kein Text sind, zu durchlaufen und ihren Typ zu überprüfen. Ich würde argumentieren (basierend auf den Ergebnissen), dass TreeWalkerdie Leistung genauso schnell ist getElementsByTagName, wenn nicht sogar schneller (selbst wenn getElementsByTagName behindert spielt).

Lief jeden Test 1000 Mal.

Methode Gesamt ms Durchschnitt ms
--------------------------------------------------
document.TreeWalker 301 0.301
Iterativer Traverser 769 0,769
Rekursiver Traverser 7352 7.352
XPath-Abfrage 1849 1.849
querySelectorAll 1725 1.725
getElementsByTagName 212 0.212

Quelle für jede Methode:

TreeWalker

function nativeTreeWalker() {
    var walker = document.createTreeWalker(
        document.body, 
        NodeFilter.SHOW_TEXT, 
        null, 
        false
    );

    var node;
    var textNodes = [];

    while(node = walker.nextNode()) {
        textNodes.push(node.nodeValue);
    }
}

Rekursive Baumdurchquerung

function customRecursiveTreeWalker() {
    var result = [];

    (function findTextNodes(current) {
        for(var i = 0; i < current.childNodes.length; i++) {
            var child = current.childNodes[i];
            if(child.nodeType == 3) {
                result.push(child.nodeValue);
            }
            else {
                findTextNodes(child);
            }
        }
    })(document.body);
}

Iterative Baumdurchquerung

function customIterativeTreeWalker() {
    var result = [];
    var root = document.body;

    var node = root.childNodes[0];
    while(node != null) {
        if(node.nodeType == 3) { /* Fixed a bug here. Thanks @theazureshadow */
            result.push(node.nodeValue);
        }

        if(node.hasChildNodes()) {
            node = node.firstChild;
        }
        else {
            while(node.nextSibling == null && node != root) {
                node = node.parentNode;
            }
            node = node.nextSibling;
        }
    }
}

querySelectorAll

function nativeSelector() {
    var elements = document.querySelectorAll("body, body *"); /* Fixed a bug here. Thanks @theazureshadow */
    var results = [];
    var child;
    for(var i = 0; i < elements.length; i++) {
        child = elements[i].childNodes[0];
        if(elements[i].hasChildNodes() && child.nodeType == 3) {
            results.push(child.nodeValue);
        }
    }
}

getElementsByTagName (Handicap)

function getElementsByTagName() {
    var elements = document.getElementsByTagName("p");
    var results = [];
    for(var i = 0; i < elements.length; i++) {
        results.push(elements[i].childNodes[0].nodeValue);
    }
}

XPath

function xpathSelector() {
    var xpathResult = document.evaluate(
        "//*/text()", 
        document, 
        null, 
        XPathResult.ORDERED_NODE_ITERATOR_TYPE, 
        null
    );

    var results = [], res;
    while(res = xpathResult.iterateNext()) {
        results.push(res.nodeValue);  /* Fixed a bug here. Thanks @theazureshadow */
    }
}

Diese Diskussion ist möglicherweise auch hilfreich - http://bytes.com/topic/javascript/answers/153239-how-do-i-get-elements-text-node


1
Ich habe gemischte Ergebnisse für jede der oben genannten Methoden in verschiedenen Browsern erhalten - diese Ergebnisse gelten für Chrome. Firefox und Safari verhalten sich sehr unterschiedlich. Ich habe leider keinen Zugriff auf IE, aber Sie können diese selbst auf IE testen, um zu sehen, ob es funktioniert. Bei der Browseroptimierung würde ich mir keine Gedanken darüber machen, für jeden Browser eine andere Methode auszuwählen, solange die Unterschiede in der Größenordnung von zehn Millisekunden oder vielleicht sogar den niedrigen Hunderten liegen.
Anurag

1
Dies ist eine wirklich nützliche Antwort, aber beachten Sie, dass die verschiedenen Methoden sehr unterschiedliche Dinge zurückgeben. Viele von ihnen erhalten nur dann Textknoten, wenn sie das erste Kind ihres Elternteils sind. Einige von ihnen können nur den Text abrufen, während andere mit geringfügigen Änderungen tatsächliche Textknoten zurückgeben können. In Iterative Tree Traversal ist ein Fehler aufgetreten, der die Leistung beeinträchtigen kann. Wechseln Sie node.nodeType = 3zunode.nodeType == 3
theazureshadow

@theazureshadow - danke, dass du auf den krassen =Fehler hingewiesen hast . Ich habe das behoben, und die xpath-Version gab einfach TextObjekte zurück und nicht die darin enthaltene tatsächliche Zeichenfolge wie die anderen Methoden. Die Methode, mit der nur der Text des ersten Kindes abgerufen wird, ist absichtlich falsch, und das habe ich am Anfang erwähnt. Ich werde die Tests erneut ausführen und die aktualisierten Ergebnisse hier veröffentlichen. Alle Tests (außer getElementsByTagName und xpath) geben die gleiche Anzahl von Textknoten zurück. XPath meldet ungefähr 20 Knoten mehr als die anderen, die ich vorerst ignorieren werde.
Anurag

6
Ich habe die Tests gleichwertig gemacht und einen jsPerf erstellt: jsperf.com/text-node-traversal
Tim Down

1
Gute Arbeit @TimDown - dieser Test für Behinderte war lange Zeit ein Augenschmerz :) Sie sollten es als Antwort hinzufügen ..
Anurag

5

Hier ist eine moderne IteratorVersion der schnellsten TreeWalker-Methode:

function getTextNodesIterator(el) { // Returns an iterable TreeWalker
    const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
    walker[Symbol.iterator] = () => ({
        next() {
            const value = walker.nextNode();
            return {value, done: !value};
        }
    });
    return walker;
}

Verwendung:

for (const textNode of getTextNodesIterator(document.body)) {
    console.log(textNode)
}

Sicherere Version

Die direkte Verwendung des Iterators kann stecken bleiben, wenn Sie die Knoten während des Loopings verschieben. Dies ist sicherer, es wird ein Array zurückgegeben:

function getTextNodes(el) { // Returns an array of Text nodes
    const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
    const nodes = [];
    while (walker.nextNode()) {
        nodes.push(walker.currentNode);
    }
    return nodes;
}

4

Ich weiß, dass Sie speziell nach einer Sammlung gefragt haben, aber wenn Sie das nur informell gemeint haben und es Ihnen egal war, ob sie alle zu einer großen Zeichenfolge zusammengefügt wurden, können Sie Folgendes verwenden:

var allTextAsString = document.documentElement.textContent || document.documentElement.innerText;

... wobei der erste Punkt der DOM3-Standardansatz ist. Beachten Sie jedoch, dass innerTextSkript- oder Stil-Tag-Inhalte in Implementierungen, die dies unterstützen (mindestens IE und Chrome) , ausgeschlossen zu sein scheinen, während textContentsie enthalten sind (in Firefox und Chrome).


1
Danke - das wollte ich aber nicht. Meine Bedürfnisse erfordern, dass ich sie vor Ort als DOM-Objekte inspizieren kann (wie das Finden ihrer Eltern usw.)
Levik

1
 document.deepText= function(hoo, fun){
        var A= [], tem;
        if(hoo){
            hoo= hoo.firstChild;
            while(hoo!= null){
                if(hoo.nodeType== 3){
                    if(typeof fun== 'function'){
                        tem= fun(hoo);
                        if(tem!= undefined) A[A.length]= tem;
                    }
                    else A[A.length]= hoo;
                }
                else A= A.concat(document.deepText(hoo, fun));
                hoo= hoo.nextSibling;
            }
        }
        return A;
    }

/ * Sie können ein Array aller untergeordneten Textknoten eines übergeordneten Elements zurückgeben oder eine Funktion übergeben und etwas (Suchen oder Ersetzen oder was auch immer) an dem vorhandenen Text ausführen.

In diesem Beispiel wird der Text der Nicht-Leerzeichen-Textknoten im Textkörper zurückgegeben:

var A= document.deepText(document.body, function(t){
    var tem= t.data;
    return /\S/.test(tem)? tem: undefined;
});
alert(A.join('\n'))

* /

Praktisch zum Suchen und Ersetzen, Hervorheben usw.


1

Hier ist eine Alternative, die etwas idiomatischer und (hoffentlich) leichter zu verstehen ist.

function getText(node) {
    // recurse into each child node
    if (node.hasChildNodes()) {
        node.childNodes.forEach(getText);
    }
    // get content of each non-empty text node
    else if (node.nodeType === Node.TEXT_NODE) {
        const text = node.textContent.trim();
        if (text) {
            console.log(text); // do something
        }
    }
}

0
var el1 = document.childNodes[0]
function get(node,ob)
{
        ob = ob || {};

        if(node.childElementCount)
        {

            ob[node.nodeName] = {}
            ob[node.nodeName]["text"] = [];
            for(var x = 0; x < node.childNodes.length;x++)
            {   
                if(node.childNodes[x].nodeType == 3)
                {
                    var txt = node.childNodes[x].nodeValue;


                    ob[node.nodeName]["text"].push(txt)
                    continue
                }
                get(node.childNodes[x],ob[node.nodeName])       
            };  
        }
        else
        {
            ob[node.nodeName]   = (node.childNodes[0] == undefined ? null :node.childNodes[0].nodeValue )
        }
        return ob
}



var o = get(el1)
console.log(o)

0

nachdem createTreeWalkerveraltet ist, können Sie verwenden

  /**
   * Get all text nodes under an element
   * @param {!Element} el
   * @return {Array<!Node>}
   */
  function getTextNodes(el) {
    const iterator = document.createNodeIterator(el, NodeFilter.SHOW_TEXT);
    const textNodes = [];
    let currentTextNode;
    while ((currentTextNode = iterator.nextNode())) {
      textNodes.push(currentTextNode);
    }
    return textNodes;
  }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.