【问题标题】:Iterate through HTML DOM and get depth遍历 HTML DOM 并获得深度
【发布时间】:2019-05-13 00:39:44
【问题描述】:

如何遍历 HTML DOM 并在 javascript 中列出所有具有深度的节点。

例子:

<div>
    <img src="foo.jpg">
    <p>
        <span>bar</span>
    </p>
</div>

会导致

  • div 0
  • img 1
  • p 1
  • 跨度 2

【问题讨论】:

    标签: javascript html dom


    【解决方案1】:

    编写一个跟踪深度的递归函数:

    function element_list(el,depth) {
        console.log(el+' '+depth);
        for(var i=0; i<el.children.length; i++) {
            element_list(el.children[i],depth+1);
        }
    }
    element_list(document,0);
    

    正如CodeiSir 指出的那样,这也会列出文本节点,但我们可以通过testing the nodeType 将它们过滤掉。此代码的变体将根据需要允许/忽略其他节点类型。

    function element_list(el,depth) {
       if (el.nodeType === 3) return;
    

    【讨论】:

    • 我花了一些时间来回答,这个答案漏掉了一些东西。当你实际使用它时,它不会呈现他想要的结果。
    • @CodeiSir 看到 OP 根本没有提供任何代码,我只是想提出一个基本的答案来指引他正确的方向。不过,关于过滤掉文本节点的要点很好。
    • 我想知道您是否在编写此内容后对其进行了编辑,但似乎没有。 el.children 将只返回元素,因此不会返回“文本节点”,尽管它会返回 SPAN 元素。如果您确实希望打印出文本节点,则必须输入el.childNodes。除此之外,这既简单又实用,尽管我希望不涉及递归的TreeWalker [developer.mozilla.org/en-US/docs/Web/API/Document/… 能够找出当前节点的深度。
    • 缩进输出的示例code
    【解决方案2】:

    请注意,其他答案是/不正确的地方......

    这也会过滤掉“TEXT”节点,而不是输出BODY标签。

    function getDef(element, def) {
      var str = ""
    
      var childs = element.childNodes
      for (var i = 0; i < childs.length; ++i) {
        if (childs[i].nodeType != 3) {
          str += childs[i].nodeName + " " + def + "<br />"
          str += getDef(childs[i], def + 1)
        }
      }
    
      return str
    }
    
    
    // Example
    document.body.innerHTML = getDef(document.body, 0)
    <div>
      <img src="foo.jpg">
      <p>
        <span>bar</span>
      </p>
    </div>

    【讨论】:

      【解决方案3】:

      是的,你可以!您必须进行迭代和一些逻辑来创建这棵树,但对于您的示例,您可以执行以下操作:

      var tracker = {};
      Array.from(document.querySelectorAll("*")).forEach(node => {
          if (!tracker[node.tagName]) tracker[node.tagName] = 1;
          else tracker[node.tagName]++;
      });
      console.log(tracker);
      

      您可以修改它以在子节点的递归子集上运行。这只是迭代整个文档。

      检查this fiddle 并打开控制台以查看tracker 的输出,其中计算并列出了标签名称。要添加深度,只需将parentNode.length 一直向上抓取即可。

      这是一个更新的脚本,我认为可以进行深度计数;

      var tracker = {};
      var depth = 0;
      var prevNode;
      Array.from(document.querySelectorAll("*")).forEach(node => {
          if (!tracker[node.tagName]) tracker[node.tagName] = 1;
          else tracker[node.tagName]++;
          console.log("Node depth:", node.tagName, depth);
          if (node.parentNode != prevNode) depth++;
          prevNode = node;
      });
      console.log(tracker);
      

      【讨论】:

        【解决方案4】:

        getElementDepth 返回节点的绝对深度(从html 节点开始),要获得两个节点之间的深度差,您只需从另一个节点减去绝对深度即可。

        function getElementDepthRec(element,depth)
        {
        	if(element.parentNode==null)
            	return depth;
            else
            	return getElementDepthRec(element.parentNode,depth+1);
        }
            
        function getElementDepth(element)
        {
            return getElementDepthRec(element,0);
        }
        
        function clickEvent() {
            alert(getElementDepth(document.getElementById("d1")));
        }
        <!DOCTYPE html>
        <html>
        <body>
        
        <div>
        	<div id="d1">
        	</div>
        </div>
        
        <button onclick="clickEvent()">calculate depth</button>
        
        </body>
        </html>

        【讨论】:

          【解决方案5】:

          我最初的解决方案是使用while 循环将每个元素沿 DOM 向上遍历以确定其深度:

          var el = document.querySelectorAll('body *'),  //all element nodes, in document order
              depth,
              output= document.getElementById('output'),
              obj;
          
          for (var i = 0; i < el.length; i++) {
            depth = 0;
            obj = el[i];
            while (obj.parentNode !== document.body) {   //walk the DOM
              depth++;
              obj = obj.parentNode;
            }
            output.textContent+= depth + ' ' + el[i].tagName + '\n';
          }
          <div>
            <img src="foo.jpg">
            <p>
              <span>bar</span>
            </p>
          </div>
          <hr>
          <pre id="output"></pre>

          我想出了一个新的解决方案,它将每个元素的深度存储在一个对象中。由于querySelectorAll() 按文档顺序返回元素,因此父节点总是出现在子节点之前。所以子节点的深度可以计算为其父节点的深度加一。

          这样,我们可以在没有递归的情况下一次性确定深度:

          var el = document.querySelectorAll('body *'),  //all element nodes, in document order
              depths= {                                  //stores the depths of each element
                [document.body]: -1                      //initialize the object
              },
              output= document.getElementById('output');
          
          for (var i = 0; i < el.length; i++) {
            depths[el[i]] = depths[el[i].parentNode] + 1;
            output.textContent+= depths[el[i]] + ' ' + el[i].tagName + '\n';
          }
          <div>
            <img src="foo.jpg">
            <p>
              <span>bar</span>
            </p>
          </div>
          <hr>
          <pre id="output"></pre>

          【讨论】:

            【解决方案6】:

            任何人都在寻找在不使用递归的情况下遍历节点下的树的东西*,但它也为您提供深度(相对于头节点)......以及任何时候的兄弟祖先坐标:

            function walkDOM( headNode ){
              const stack = [ headNode ];
              const depthCountDowns = [ 1 ];
              while (stack.length > 0) {
                const node = stack.pop();
                console.log( '\ndepth ' + ( depthCountDowns.length - 1 ) + ', node: ');
                console.log( node );
            
                let lastIndex = depthCountDowns.length - 1;
                depthCountDowns[ lastIndex ] = depthCountDowns[ lastIndex ] - 1;
            
                if( node.childNodes.length ){
                    depthCountDowns.push( node.childNodes.length );
                    stack.push( ... Array.from( node.childNodes ).reverse() );
                }
            
                while( depthCountDowns[ depthCountDowns.length - 1 ] === 0 ){
                    depthCountDowns.splice( -1 );
                }
              }
            } 
            
            walkDOM( el );
            

            PS 可以理解,我已经输入了&gt; 0=== 0 以试图提高清晰度...首先可以省略,第二个当然可以替换为前导!

            * 看看 here 了解关于 JS 中递归成本的骇人听闻的真相(无论如何,2018-02-01 的当代实现!)

            【讨论】:

              【解决方案7】:

              您可以使用 Jquery 来做到这一点

              $('#divId').children().each(function () {
                   // "this" is the current element
              });
              

              html 应该是这样的:

              <div id="divId">
              <img src="foo.jpg">
              <p>
                  <span>bar</span>
              </p>
              

              【讨论】:

              • 将 jQuery 引入一个不需要它的问题不是一个好主意。它可以很快被否决。此外,这仅提醒(使用 console.log)元素的value(这些元素都没有,因为它们不是输入元素),而不是它们的文档节点深度。
              • 是的,这是真的,但“this”是当前元素。我将删除该值
              【解决方案8】:
              (() => {
                  const el = document.querySelectorAll('body *');
                  const depths = new Map();
                  depths.set(document.body, -1)
                  el.forEach((e) => {
                      const p = e.parentNode;
                      const d = depths.get(p);
                      depths.set(e, d + 1);
                  })
                  return depths;
              })()
              

              这是Rick Hitchcocks 的答案,但使用地图而不是对象

              Map(5) {body =&gt; -1, div =&gt; 0, img =&gt; 1, p =&gt; 1, span =&gt; 2} 中的结果

              【讨论】:

                猜你喜欢
                • 2012-12-09
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-08-26
                • 2016-08-24
                • 1970-01-01
                相关资源
                最近更新 更多