与textNodes等效的getElementsByTagName()


79

有什么方法可以获取textNode文档中所有对象的集合?

getElementsByTagName()对于Elements来说效果很好,但textNodes不是Elements。

更新:我意识到这可以通过遍历DOM来完成-如以下建议。我知道如何编写一个DOM-walker函数来查看文档中的每个节点。我希望有一些浏览器本机的方法可以做到这一点。毕竟,我可以<input>通过一个内置调用获得所有s,但不是全部textNodes有点奇怪。

Answers:


116

更新

我已经概述了这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次。

方法总ms平均ms
--------------------------------------------------
document.TreeWalker 301 0.301
迭代遍历769 0.769
递归遍历器7352 7.352
XPath查询1849 1.849
querySelector全部1725 1.725
getElementsByTagName 212 0.212

每种方法的来源:

树行者

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上测试这些功能,以查看其是否有效。至于浏览器的优化,我不会担心为每个浏览器选择不同的方法,只要差异在几十毫秒甚至是几百毫秒之间即可。
阿努拉格2010年

1
这是一个非常有用的答案,但是请注意,不同的方法返回的结果非常不同。如果他们是父母的第一个孩子,他们中的许多人只会获得文本节点。它们中的一些只能获取文本,而其他一些则可以返回实际文本节点并进行少量修改。迭代树遍历中有一个错误可能会影响其性能。更改node.nodeType = 3node.nodeType == 3
theazureshadow 2012年

@theazureshadow-感谢您指出明显的=错误。我已经解决了这个问题,并且xpath版本只是返回Text对象,而不是像其他方法一样返回其中包含的实际字符串。仅获取第一个孩子的文本的方法是故意错误的,而我在一开始已经提到过。我将重新运行测试,并将更新的结果发布在此处。所有测试(getElementsByTagName和xpath除外)都返回相同数量的文本节点。XPath报告的节点大约比我现在将忽略的其他节点多20个。
阿努拉格2012年

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 2011年

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

/ *您可以返回某个父元素的所有后代文本节点的数组,也可以将其传递给某些函数,然后对文本进行某些操作(查找或替换或其他操作)。

此示例返回正文中非空白textnode的文本:

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;
  }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.