【问题标题】:How to print selectors of DOM objects如何打印 DOM 对象的选择器
【发布时间】:2018-01-30 13:30:59
【问题描述】:

给定这样的 DOM 结构:

<div>
  <div>
    <span>
      <img/>
      <i>
        <span></span>
        <meter></meter>
      </i>
      <a><span></span></a>
    </span>
  </div>
  <nav>
    <form>
      <input/>
      <button></button>
    </form>
  </nav>
</div>

想知道你是如何接受它,然后返回一个包含所有选择器的平面数组:

[
  'div > div > span > img',
  'div > div > span > i > span',
  'div > div > span > i > meter',
  'div > div > span > a > span',
  'div > nav > form > input',
  'div > nav > form > button'
]

我的尝试没有成功:

function outputSelectors(array, node) {
  var tag = node.tagName
  array.push(tag)
  for (var i = 0, n = node.children.length; i < n; i++) {
    var child = node.children[i]
    outputSelectors(array, child)
  }
}

outputSelectors([], document.body.children[0])

不知道从这里去哪里。

【问题讨论】:

  • 你只想要叶子节点的选择器吗?
  • 是的,只有叶节点。
  • ^ 我只是想把它链接起来。使用该代码获取从document.querySelectorAll("*")返回的每个元素的路径
  • 我想知道如何做算法,而不是使用内置方法。

标签: javascript arrays combinations


【解决方案1】:

一种可能的,一种从顶部(准确地说是根)到底部的非递归方法:

function collectLeafNodePathes(root) {
  const paths = [];
  const selectorParts = [];
  let el = root;
  while (el) {
    const tagName = el.tagName.toLowerCase();
    if (el.childElementCount) {
      selectorParts.push(tagName);
      el = el.firstElementChild;
      continue;
    }

    paths.push(selectorParts.concat([tagName]).join(' > '));
    do {
       if (el.nextElementSibling) {
         el = el.nextElementSibling;
         break;
       }
       el = el.parentNode;
       selectorParts.pop();         
       if (el === root) {
         el = null;
       }
    } while (el);
  }
  return paths;
}

const selectors = collectLeafNodePathes(document.getElementById('xxx'));
console.log(selectors);
<div id="xxx">
  <div>
    <span>
      <img/>
      <i>
        <span></span>
        <meter></meter>
      </i>
      <a><span></span></a>
    </span>
  </div>
  <nav>
    <form>
      <input/>
      <button></button>
    </form>
  </nav>
</div>

不过,最后一部分(do-while 循环)的边缘有点粗糙;对任何改进持开放态度。

我使用了辅助属性(childElementCountfirstElementChildnextElementSibling)来跳过检查文本节点和内容。如果这不是一个选项(由于兼容性原因),则很容易实现 polyfill 或只是在非元素节点上“倒回”循环。

【讨论】:

  • 代替jsFiddle,为什么不用栈sn-p?
  • 因为sn-ps比较麻烦。我会用 sn-p 更新我的答案,但用 fiddle 构建原型更容易——至少对我来说是这样。
【解决方案2】:

您可以使用this answer 中的getPath 方法映射页面上的所有元素。

最好在您自己的控制台中尝试,因为 sn-p 需要一些时间才能运行,而且 sn-p 的控制台似乎无法正确处理输出。

jQuery.fn.extend({
    getPath: function () {
        var path, node = this;
        while (node.length) {
            var realNode = node[0], name = realNode.localName;
            if (!name) break;
            name = name.toLowerCase();

            var parent = node.parent();

            var sameTagSiblings = parent.children(name);
            if (sameTagSiblings.length > 1) { 
                allSiblings = parent.children();
                var index = allSiblings.index(realNode) + 1;
                if (index > 1) {
                    name += ':nth-child(' + index + ')';
                }
            }

            path = name + (path ? '>' + path : '');
            node = parent;
        }

        return path;
    }
});

const allElements = $("*");
const allPaths = allElements.map((_, e) => $(e).getPath());

console.log(allPaths);
&lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"&gt;&lt;/script&gt;

这是一个没有 jQuery 的版本,如果更可取的话:

function getPath (node) {
    var path;
    while (node.parentElement) {
        var name = node.localName;
        if (!name) break;
        name = name.toLowerCase();

        var parent = node.parentElement;

        var sameTagSiblings = [...parent.children].filter(e => e.localName === name);
        if (sameTagSiblings.length > 1) { 
            allSiblings = parent.children;
            var index = [...allSiblings].indexOf(node) + 1;
            if (index > 1) {
                name += ':nth-child(' + index + ')';
            }
        }

        path = name + (path ? '>' + path : '');
        node = parent;
    }

    return path;
};

const allElements = document.querySelectorAll("*");
const allPaths = [...allElements].map(e => getPath(e));

console.log(allPaths);

【讨论】:

  • 所以这个算法本质上是找到每个叶子节点,然后建立一条到顶部的路径。有趣的。这消除了遍历节点的算法部分,即 jquery 中的*。想知道是否有一种方法可以自上而下地构建它,也许这是错误的方法。
  • 为什么选择 jQuery?来自 OP:我想知道如何执行算法,而不是使用内置方法。
  • @Ele:里面没有太多的 jQuery。只是更易读的代码。算法就在那里,用纯 JS 编写。
  • 我知道,但我觉得jQuery插件的用法很冗长。
  • @Ele:我添加了一个非 jQuery 版本 :-)
【解决方案3】:

稍微修改this解决方案获取路径,this获取叶节点。

function getPath(node) 
{
  var path;
  while (node.parentNode ) 
  {
    name = node.nodeName;
    if (!name) break;

    var parent = node.parentNode;
    path = name + (path ? ' > ' + path : '');
    node = parent;
  }
  return path;
}

function getLeafNodes() 
{
    var allNodes = document.getElementsByTagName("*");
    var leafNodes = Array.from( allNodes ).filter(function(elem) {
        return !elem.hasChildNodes();
    });
    return leafNodes;
}

var leadNodes = getLeafNodes() ;
var output = leadNodes.map( s => getPath(s) );
console.log(output);
<div>
  <div>
    <span>
      <img/>
      <i>
        <span></span>
        <meter></meter>
      </i>
      <a><span></span></a>
    </span>
  </div>
  <nav>
    <form>
      <input/>
      <button></button>
    </form>
  </nav>
</div>

【讨论】:

  • 如果你有重复的兄弟姐妹怎么办?那么你将不再有有效的选择器。
  • OP 没有在他的问题中要求唯一选择器。
  • 嗯,这可行,但我想尝试自上而下地进行,类似于:github.com/hughsk/flat/blob/…,希望得到这些方面的答案。想不通。
  • @gurvinder372:是的,但这在 HTML 中并不少见
【解决方案4】:

您可以创建递归函数并使用children() 方法检查当前元素是否包含子元素。

const result = []

const getTag = (el) => el.prop('tagName').toLowerCase()
function print(el, prev = '') {
  prev = prev.length ? prev : getTag(el)
  const children = el.children();
  if(!children.length) result.push(prev)
  else {
    children.each(function() {
    	let tag = getTag($(this))
      let str = prev + (prev.length ? ' > ' : '') + tag;
      print($(this), str)
    })
  }
}

print($('#start'))
console.log(result)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="start">
  <div>
    <span>
      <img/>
      <i>
        <span></span>
        <meter></meter>
      </i>
      <a><span></span></a>
    </span>
  </div>
  <nav>
    <form>
      <input/>
      <button></button>
    </form>
  </nav>
</div>

要获得唯一选择器数组,您可以在最终结果上使用Set 来删除重复项。

let result = []

const getTag = (el) => el.prop('tagName').toLowerCase()
function print(el, prev = '') {
  prev = prev.length ? prev : getTag(el)
  const children = el.children();
  if(!children.length) result.push(prev)
  else {
    children.each(function() {
    	let tag = getTag($(this))
      let str = prev + (prev.length ? ' > ' : '') + tag;
      print($(this), str)
    })
  }
}

print($('#start'))
result = [...new Set(result)]
console.log(result)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="start">
  <div>
    <span>
      <img/>
      <i>
        <span></span>
        <meter></meter>
      </i>
      <a><span></span></a>
      <a><span></span></a>
    </span>
  </div>
  <nav>
    <form>
      <input/>
      <button></button>
    </form>
  </nav>
</div>

【讨论】:

  • 如果你有重复的兄弟姐妹怎么办?那么你将不再有有效的选择器。
  • @Cerbrus 你能举个例子吗
  • &lt;a&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;a&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;替换&lt;a&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;
  • 你会得到两个"div &gt; div &gt; span &gt; a &gt; span" jsfiddle.net/Lg0wyt9u/2712
  • 这是无效的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-10
  • 1970-01-01
  • 1970-01-01
  • 2015-10-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多