getElementsByTagName () эквивалент для textNodes

79

Есть ли способ получить коллекцию всех textNodeобъектов в документе?

getElementsByTagName()отлично работает с элементами, но textNodeне элементы.

Обновление: я понимаю, что это можно сделать, пройдя DOM - как предлагают многие ниже. Я знаю, как написать функцию DOM-walker, которая просматривает каждый узел в документе. Я надеялся, что это можно сделать с помощью браузера. В конце концов, это немного странно, что я могу получить все <input>с одним встроенным вызовом, но не все textNode.

левик
источник

Ответы:

117

Обновление :

Я обрисовал в общих чертах некоторые базовые тесты производительности для каждого из этих 6 методов за 1000 запусков. getElementsByTagNameявляется самым быстрым, но он выполняет половинчатую работу, поскольку он не выбирает все элементы, а только один конкретный тип тега (я думаю p) и вслепую предполагает, что его firstChild является текстовым элементом. Это может быть немного ошибочно, но оно предназначено для демонстрации и сравнения его производительности TreeWalker. Проведите тесты на jsfiddle, чтобы увидеть результаты.

  1. Использование TreeWalker
  2. Пользовательский итеративный обход
  3. Пользовательский рекурсивный обход
  4. Xpath запрос
  5. querySelectorAll
  6. getElementsByTagName

Предположим на мгновение, что существует метод, позволяющий получить все Textузлы изначально. Вам все равно придется обходить каждый результирующий текстовый узел и вызывать node.nodeValueдля получения фактического текста, как если бы вы поступали с любым узлом DOM. Таким образом, проблема производительности заключается не в итерации текстовых узлов, а в повторении всех узлов, не являющихся текстовыми, и проверке их типа. Я бы поспорил (основываясь на результатах), что он TreeWalkerработает так же быстро getElementsByTagName, если не быстрее (даже если getElementsByTagName играет с ограниченными возможностями).

Выполните каждый тест 1000 раз.

Метод Всего мс Среднее мс
--------------------------------------------------
document.TreeWalker 301 0,301
Итерационный Traverser 769 0,769
Рекурсивный Traverser 7352 7.352
Запрос XPath 1849 1,849
querySelectorAll 1725 1,725
getElementsByTagName 212 0,212

Источник для каждого метода:

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);
    }
}

Рекурсивный обход дерева

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);
}

Итерационный обход дерева

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 (гандикап)

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 */
    }
}

Кроме того, это обсуждение может оказаться полезным - http://bytes.com/topic/javascript/answers/153239-how-do-i-get-elements-text-node

Анураг
источник
1
Я получил смешанные результаты для каждого из вышеперечисленных методов в разных браузерах - эти результаты выше для Chrome. Firefox и Safari ведут себя по-разному. К сожалению, у меня нет доступа к IE, но вы можете сами протестировать их в IE, чтобы увидеть, работает ли он. Что касается оптимизации браузера, я бы не стал беспокоиться о выборе другого метода для каждого браузера, если различия составляют порядка десятков миллисекунд или, возможно, даже нескольких сотен.
Anurag
1
Это действительно полезный ответ, но имейте в виду, что разные методы возвращают очень разные вещи. Многие из них получают текстовые узлы только в том случае, если они являются первым потомком своего родителя. Некоторые из них могут получать только текст, в то время как другие могут возвращать фактические текстовые узлы с небольшими изменениями. В итеративном обходе дерева есть ошибка, которая может повлиять на его производительность. Изменить node.nodeType = 3наnode.nodeType == 3
theazureshadow 05
@theazureshadow - спасибо, что указали на явную =ошибку. Я исправил это, и версия xpath просто возвращала Textобъекты, а не фактическую строку, содержащуюся в ней, как это делали другие методы. Метод, который получает текст только первого потомка, намеренно неправильный, и я упоминал об этом в начале. Я повторно проведу тесты и опубликую обновленные результаты здесь. Все тесты (кроме getElementsByTagName и xpath) возвращают одинаковое количество текстовых узлов. XPath сообщает примерно на 20 узлов больше, чем другие, которые я пока игнорирую.
Anurag
6
Я сделал тесты эквивалентными и сделал jsPerf: jsperf.com/text-node-traversal
Tim Down
1
Хорошая работа @TimDown - этот тест для инвалидов долгое время вызывал боль :) Вы должны добавить его в качестве ответа ..
Anurag
5

Вот современная Iteratorверсия самого быстрого метода TreeWalker:

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;
}

Применение:

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

Более безопасная версия

Непосредственное использование итератора может застрять, если вы перемещаете узлы во время цикла. Это безопаснее, он возвращает массив:

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;
}
Fregante
источник
4

Я знаю, что вы специально просили коллекцию, но если вы имели в виду это неформально и не заботились о том, были ли все они объединены в одну большую строку, вы можете использовать:

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

... с первым элементом стандартного подхода DOM3. Однако обратите внимание, чтоinnerText похоже, исключает содержимое тега сценария или стиля в реализациях, которые его поддерживают (по крайней мере, IE и Chrome), но textContentвключает их (в Firefox и Chrome).

Бретт Замир
источник
1
Спасибо, хотя я этого не хотел. Мои потребности заключаются в том, чтобы иметь возможность проверить их на месте как объекты DOM (например, найти их родителей и т. Д.)
левик
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;
    }

/ * Вы можете вернуть массив всех текстовых узлов-потомков некоторого родительского элемента, или вы можете передать ему некоторую функцию и сделать что-нибудь (найти, заменить или что-то еще) с текстом на месте.

Этот пример возвращает текст текстовых узлов без пробелов в теле:

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

* /

Удобен для поиска и замены, выделения и т. Д.

Kennebec
источник
1

Вот альтернатива, которая немного более идиоматична и (надеюсь) легче для понимания.

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
        }
    }
}
Jtschoonhoven
источник
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

после того, createTreeWalkerкак устарел, вы можете использовать

  /**
   * 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;
  }
Зухаир Таха
источник