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

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

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

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


person levik    schedule 05.04.2010    source источник


Ответы (6)


Обновление:

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

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

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

Ran each test 1000 times.

Method                  Total ms        Average ms
--------------------------------------------------
document.TreeWalker          301            0.301
Iterative Traverser          769            0.769
Recursive Traverser         7352            7.352
XPath query                 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

person Anurag    schedule 05.04.2010
comment
Я получил смешанные результаты для каждого из вышеперечисленных методов в разных браузерах - эти результаты выше для Chrome. Firefox и Safari ведут себя по-разному. К сожалению, у меня нет доступа к IE, но вы можете сами протестировать их в IE, чтобы увидеть, работает ли он. Что касается оптимизации браузера, я бы не стал беспокоиться о выборе разных методов для каждого браузера, если различия составляют порядка десятков миллисекунд или, может быть, даже нескольких сотен. - person Anurag; 07.04.2010
comment
Это действительно полезный ответ, но имейте в виду, что разные методы возвращают очень разные вещи. Многие из них получают текстовые узлы только в том случае, если они являются первым потомком своего родителя. Некоторые из них могут получать только текст, в то время как другие могут возвращать фактические текстовые узлы с небольшими изменениями. Ошибка в итеративном обходе дерева, которая может повлиять на его производительность. Измените node.nodeType = 3 на node.nodeType == 3 - person theazureshadow; 05.07.2012
comment
@theazureshadow - спасибо, что указали на вопиющую ошибку =. Я исправил это, и версия xpath просто возвращала Text объектов, а не фактическую строку, содержащуюся в нем, как это делали другие методы. Метод, который получает только текст первого потомка, намеренно неправильный, и я уже упоминал об этом в начале. Я повторно проведу тесты и опубликую обновленные результаты здесь. Все тесты (кроме getElementsByTagName и xpath) возвращают одинаковое количество текстовых узлов. XPath сообщает примерно на 20 узлов больше, чем другие, которые я пока игнорирую. - person Anurag; 06.07.2012
comment
Метод селектора запроса также сообщит о немного другом количестве текстовых узлов, поскольку querySelector не может запрашивать текстовые узлы. Такой запрос, как body *, найдет только дочерние элементы (не текстовые узлы) внутри основного элемента. - person Anurag; 06.07.2012
comment
@Anurag: Да, вам нужно пройти через все элементы и получить каждый из их текстовых узлов. Он также должен проходить через текстовые узлы основного элемента. Изменить на querySelectorAll('body, body *') - person theazureshadow; 07.07.2012
comment
@theazureshadow - да, конечно. Спасибо, что указали на это;) - person Anurag; 07.07.2012
comment
IE 9+ поддерживает TreeWalkers ... = \ MDN - createTreeWalker - person Wes Alvaro; 18.03.2013
comment
Я сделал тесты эквивалентными и создал jsPerf: jsperf.com/text-node-traversal - person Tim Down; 19.04.2013
comment
Хорошая работа @TimDown - этот тест для инвалидов долгое время вызывал боль :) Вы должны добавить его в качестве ответа .. - person Anurag; 19.04.2013
comment
Помещение всех узлов в массив - это то, чего вы наверняка избежите в критически важном для производительности коде, поэтому я удалил эти очень дорогие вызовы, просто подсчитав количество узлов. Сначала выходит createTreeWalker: jsperf.com/text-node-traversal/5. Цифры различаются в трех случаях (ограниченный набор тестовых случаев), поэтому результаты различаются! - person citykid; 28.08.2013
comment
Насчет XPath //*/text(), почему не //text()? Настоящий перевод всех объектов textNode в документе. - person Peter Krauss; 05.07.2014
comment
NodeFilter не поддерживается в IE, что делает производительность обходчика дерева в лучшем случае сомнительной: developer.mozilla.org/en-US/docs/Web/API/ - person James Westgate; 06.11.2014
comment
OMG, я понятия не имел о document.createTreeWalker - похоже, фантастический метод! Кроме того, ваш настраиваемый итеративный ходунок открывает глаза - он намного более эффективен и на самом деле его легче читать / понимать, чем обычная рекурсивная версия замены! - person mike nelson; 25.06.2016
comment
"//*/text()", необходимо, или //text() тоже подойдет? - person ; 14.01.2017
comment
Для метода итеративного обхода дерева, описанного выше, если у document.body есть следующий брат, например, <script>, то обход перейдет к брату body и в конечном итоге сгенерирует TypeError: node is null. - person thebat; 19.12.2018
comment
Ссылки jsfiddle / jsperf мертвы. - person QHarr; 05.06.2021

Вот современная 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;
}
person fregante    schedule 13.06.2017

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

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

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

person Brett Zamir    schedule 01.04.2011
comment
Спасибо, но я не этого хотел. Мои потребности заключаются в том, чтобы иметь возможность проверить их на месте как объекты DOM (например, найти их родителей и т. Д.) - person levik; 21.04.2011

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

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
        }
    }
}
person jtschoonhoven    schedule 09.03.2019

 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'))

*/

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

person kennebec    schedule 05.04.2010

после того, как 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;
  }
person Zuhair Taha    schedule 07.08.2020
comment
Я не думаю, что это рекурсивно ... - person RedGuy11; 10.03.2021