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


79

Чи є спосіб отримати колекцію всіх textNodeоб’єктів у документі?

getElementsByTagName()чудово працює для Elements, але textNodes не є Elements.

Оновлення: Я усвідомлюю, що цього можна досягти, пройшовши DOM - як пропонують багато внизу. Я знаю, як написати функцію DOM-walker, яка переглядає кожен вузол у документі. Я сподівався, що існує якийсь власний браузер. Зрештою, трохи дивно, що я можу отримати всі <input>s за допомогою одного вбудованого дзвінка, але не всі textNodes.

Відповіді:


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
Ітеративний траверс 769 0,769
Рекурсивний траверс 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, щоб перевірити, чи працює це. Що стосується оптимізації браузера, я б не турбувався про вибір іншого методу для кожного браузера, якщо різниці складають десятки мілісекунд або, можливо, навіть сотні.
Анураг,

1
Це дійсно корисна відповідь, але пам’ятайте, що різні методи повертають дуже різні речі. Багато з них отримують текстові вузли лише в тому випадку, якщо вони перші діти дочірніх батьків. Деякі з них можуть отримати лише текст, тоді як інші можуть повернути фактичні текстові вузли з незначними змінами. У Iterative Tree Traversal є помилка, яка може вплинути на її ефективність. Перехід node.nodeType = 3наnode.nodeType == 3
theazureshadow

@theazureshadow - дякую, що вказали на кричущу =помилку. Я це виправив, і версія xpath просто повертала Textоб'єкти, а не фактичний рядок, що міститься в ньому, як це робили інші методи. Метод, який отримує лише текст першої дитини, навмисно помилковий, і я згадував про це на початку. Я повторно запущу тести та опублікую оновлені результати тут. Усі тести (крім getElementsByTagName та xpath) повертають однакову кількість текстових вузлів. XPath повідомляє про приблизно 20 вузлів більше, ніж інші, які я поки що ігнорую.
Анураг

6
Я зробив тести еквівалентними і зробив jsPerf: jsperf.com/text-node-traversal
Tim Down

1
Приємна робота @TimDown - цей тест на інвалідність довго тривав у очах :) Ви повинні додати його як відповідь ..
Анураг,

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

4

Я знаю, що ви спеціально попросили колекцію, але якщо ви просто мали на увазі це неофіційно і вам було байдуже, чи всі вони об’єднані в одну велику низку, ви можете використовувати:

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

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


1
Дякую - це не те, що я хотів. Мої потреби вимагають можливості перевірити їх на місці як об'єкти DOM (наприклад, знайти батьків тощо)
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;
    }

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

Цей приклад повертає текст непробільних текстових вузлів у тілі:

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

* /

Зручно для пошуку та заміни, виділення тощо


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

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;
  }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.