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

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

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

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

Ответы

Ответ 1

Обновление

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

  • Использование TreeWalker
  • Пользовательский итеративный обход
  • Пользовательский рекурсивный обход
  • Запрос Xpath
  • querySelectorAll
  • getElementsByTagName

Предположим на мгновение, что существует метод, который позволяет получить все узлы Text изначально. Вам все равно придется пересекать каждый полученный текст node и вызывать node.nodeValue, чтобы получить фактический текст так же, как и с любым DOM Node. Таким образом, проблема производительности связана не с итерацией через текстовые узлы, а с повторением через все узлы, которые не являются текстом и проверяют их тип. Я бы поспорил (на основе результатов), что 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

Ответ 2

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

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

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

Ответ 3

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

*/

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

Ответ 4

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)

Ответ 5

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

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

Использование:

const textNodes = [...getTextNodesIterator(document.body)];

Или более интересно, с циклом for-of:

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