【问题标题】:getElementsByTagName() equivalent for textNodes文本节点的 getElementsByTagName() 等效项
【发布时间】:2011-02-04 11:58:24
【问题描述】:

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

getElementsByTagName() 非常适合 Elements,但 textNodes 不是 Elements。

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

【问题讨论】:

    标签: javascript dom dhtml textnode


    【解决方案1】:

    更新

    我已经为这 6 种方法中的每一种在 1000 次以上的运行中概述了一些基本性能测试。 getElementsByTagName 是最快的,但它完成了一半的工作,因为它不会选择所有元素,而只选择一种特定类型的标签(我认为p)并且盲目地假设它的 firstChild 是一个文本元素。它可能有点缺陷,但它用于演示目的并将其性能与TreeWalker 进行比较。 Run the tests yourselves on jsfiddle查看结果。

    1. 使用 TreeWalker
    2. 自定义迭代遍历
    3. 自定义递归遍历
    4. Xpath 查询
    5. querySelectorAll
    6. getElementsByTagName

    让我们暂时假设有一种方法可以让您以本机方式获取所有Text 节点。您仍然需要遍历每个生成的文本节点并调用node.nodeValue 来获取实际文本,就像您对任何 DOM 节点所做的那样。所以性能问题不在于遍历文本节点,而是遍历所有非文本节点并检查它们的类型。我认为(基于结果)TreeWalker 的执行速度与getElementsByTagName 一样快,即使不是更快(即使 getElementsByTagName 玩有障碍)。

    每个测试运行 1000 次。 方法 总毫秒 平均毫秒 -------------------------------------------------- 文档.TreeWalker 301 0.301 迭代遍历器 769 0.769 递归遍历器 7352 7.352 XPath 查询 1849 1.849 查询选择器全部 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

    【讨论】:

    • 我在不同的浏览器中得到了上述每种方法的混合结果 - 以上结果适用于 Chrome。 Firefox 和 Safari 的行为非常不同。不幸的是,我无法访问 IE,但您可以自己在 IE 上进行测试,看看它是否有效。至于浏览器优化,我不会担心为每个浏览器选择不同的方法,只要差异在几十毫秒甚至几百毫秒。
    • 这是一个非常有用的答案,但要注意不同的方法会返回非常不同的东西。如果他们是他们父母的第一个孩子,他们中的许多人只会获得文本节点。其中一些只能获取文本,而另一些则可以返回实际的文本节点,只需稍作修改。 Iterative Tree Traversal 中存在可能影响其性能的错误。将node.nodeType = 3 更改为node.nodeType == 3
    • @theazureshadow - 感谢您指出明显的 = 错误。我已经解决了这个问题,xpath 版本只是返回 Text 对象,而不是像其他方法那样返回其中包含的实际字符串。只获取第一个孩子的文本的方法是故意错误的,我在开头已经提到过。我将重新运行测试,并在此处发布更新的结果。所有测试(getElementsByTagName 和 xpath 除外)都返回相同数量的文本节点。 XPath 报告的节点比我现在忽略的其他节点多 20 个。
    • 我已经使测试等效并制作了一个 jsPerf:jsperf.com/text-node-traversal
    • 干得好@TimDown - 残疾人测试很长一段时间都令人眼花缭乱:) 你应该将其添加为答案..
    【解决方案2】:
     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'))
    

    */

    方便搜索和替换、突出显示等

    【讨论】:

      【解决方案3】:

      我知道您特别要求收集一个集合,但如果您只是非正式的意思并且不在乎它们是否全部连接成一个大字符串,您可以使用:

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

      ...第一项是 DOM3 标准方法。但是请注意,innerText 似乎在支持它的实现(至少 IE 和 Chrome)中排除了脚本或样式标记内容,而textContent 包括它们(在 Firefox 和 Chrome 中)。

      【讨论】:

      • 谢谢 - 这不是我想要的。我的需求是能够将它们作为 DOM 对象就地检查(比如找到它们的父对象等)
      【解决方案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】:

        这是最快的 TreeWalker 方法的现代 Iterator 版本:

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

        【讨论】:

          【解决方案6】:

          这里有一个更惯用的替代方法,并且(希望)更容易理解。

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

          【讨论】:

            【解决方案7】:

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

            【讨论】:

              猜你喜欢
              • 2018-06-22
              • 2021-01-14
              • 2010-11-30
              • 1970-01-01
              • 1970-01-01
              • 2019-03-04
              • 1970-01-01
              • 2020-01-17
              • 2013-05-04
              相关资源
              最近更新 更多