获取子节点的最佳方法

272

我在想,JavaScript提供了多种方法来获取任何元素的第一个子元素,但哪个是最好的呢?通过“最好”,我指的是:在跨浏览器兼容性、速度、全面性和行为方面最可靠。以下是我使用的方法/属性别名列表:

var elem = document.getElementById('container');
var child = elem.children[0];
var child = elem.firstElementChild; // == children[0]

这对两种情况都适用:

var child = elem.childNodes[0]; // or childNodes[1], see below

那是针对表单或 <div> 迭代的情况。如果我可能遇到文本元素:

var child = elem.childNodes; // treat as NodeList
var child = elem.firstChild;
据我所知,firstChild 使用来自 childNodes 的 NodeList,而 firstElementChild 使用 children。我基于 MDN 参考文献得出这个假设:

childNode 是元素节点的第一个子元素的引用,如果没有子元素,则为 null

就速度而言,我猜想差异(如果有的话)几乎不存在,因为 firstElementChild 实际上是 children[0] 的引用,而 children 对象已经在内存中了。
令我困惑的是 childNodes 对象。我使用它来查看表格元素中的表单,但是 children 列出了所有表单元素,而 childNodes 似乎还包括 HTML 代码中的空白符:
console.log(elem.childNodes[0]);
console.log(elem.firstChild);

两者日志记录 <TextNode textContent="\n ">

console.log(elem.childNodes[1]);
console.log(elem.children[0]);
console.log(elem.firstElementChild);
所有的日志 <input type="text">。为什么?我理解一个对象可以让我使用原始的HTML代码,而另一个对象则依附于DOM,但似乎 childNodes 元素在两个层次上都能工作。
回到我的最初问题,我的猜测是:如果我想要最全面的对象,则 childNodes 是正确的选择,但是由于其全面性,在任何给定时刻返回我想要/期望的元素可能不太可预测。在这种情况下,跨浏览器支持可能也会成为挑战,尽管我可能是错的。
有人能澄清手头对象之间的区别吗?如果有速度差异,无论多么微不足道,我也想知道。如果我看错了,请随时指出。
PS:请务必留下 JavaScript,所以是的,我想处理这种事情。像“jQuery为您处理此事”这样的答案不是我要找的,因此没有 标签。
5个回答

257
听起来你在过度思考了。你已经观察到了childNodeschildren之间的区别,即childNodes 包含所有节点,包括仅由空格组成的文本节点,而children是仅包含子元素节点的集合。就这些。
对于这两个集合都没有什么不可预测之处,但需要注意以下几个问题:
  • IE <= 8不会在childNodes中包含仅有空格的文本节点,而其他浏览器则会包含。
  • IE <= 8会在children中包含注释节点,而其他浏览器只包含元素节点。
childrenfirstElementChild及其它的方法都只是方便使用的工具,以过滤DOM并仅限于元素节点的视图。

根据quirksmode的说法,@TimDown,如果你需要跨浏览器尤其是IE<9,那就比这更复杂了:http://quirksmode.org/dom/core/ - Christophe
@Christophe:怎么了?我在我的回答中提到了IE <= 8中与仅包含空格的文本节点有关的问题。 - Tim Down
2
@Christophe:啊,说得好,谢谢。我会在我的答案中添加一条注释。考虑到children是在IE 4中诞生的,比成为标准或被其他浏览器采用早了十年,你可以认为IE <= 8是正确的,而其他浏览器是错误的。 - Tim Down
值得注意的是,在.children上使用for in循环会导致意外的结果,因为子对象上有命名键。即length__proto__。可能显而易见,但它让我困惑了一段时间... - shennan
@shennan:这就是为什么在我的问题中,我在评论中写了“将其视为Nodelist”。NodeList是一个类似数组的对象,一些浏览器(如FF)作为JS和DOM API超集的一部分实现了自定义对象。但重点是:如果它是类似数组的实例,请勿使用for...in,或者使用for (p in inst) { if (inst.hasOwnProperty(p)) { /* do stuff */}}过滤循环。 - Elias Van Ootegem
显示剩余7条评论

24

firstElementChild 在 IE<9 中可能不可用(只有 firstChild

在 IE<9 中,firstChildfirstElementChild,因为 MS DOM(IE<9)不存储空文本节点。但是在其他浏览器上这样做会返回空文本节点...

我的解决方案

child=(elem.firstElementChild||elem.firstChild)

这将在IE<9上给出firstchild

1
firstElementChild 在非 IE 浏览器中也不一定是安全的:仅有 Firefox 在 3.5 版本开始支持它。 - Tim Down
1
Tim是正确的,当我在谷歌上搜索这些对象的东西时,这个网站出现了,是一个不错的网站,值得偶尔查看。 - Elias Van Ootegem
+1 正是我所需要的。与被接受的答案相反,它考虑了 IE<9。 - Christophe
@Christophe:有点苛刻了:这个答案和我的一样,也没有考虑IE < 9的情况。 - Tim Down
@TimDown,我不是有意冒犯,如果听起来像那样,对不起。我专注于我的当前项目,children()由于其在IE<9中处理注释的方式(再次基于我在Quirksmode上读到的内容)而不符合要求。 - Christophe
显示剩余4条评论

23

跨浏览器的方法是使用childNodes来获取NodeList,然后用nodeTypeELEMENT_NODE的所有节点创建一个数组。

/**
 * Return direct children elements.
 *
 * @param {HTMLElement}
 * @return {Array}
 */
function elementChildren (element) {
    var childNodes = element.childNodes,
        children = [],
        i = childNodes.length;

    while (i--) {
        if (childNodes[i].nodeType == 1) {
            children.unshift(childNodes[i]);
        }
    }

    return children;
}

http://jsfiddle.net/s4kxnahu/

如果你使用实用库(如lodash),这会变得特别容易:

/**
 * Return direct children elements.
 *
 * @param {HTMLElement}
 * @return {Array}
 */
function elementChildren (element) {
    return _.where(element.childNodes, {nodeType: 1});
}

未来:

您可以结合:scope伪类(匹配选择器的参考元素)使用querySelectorAll

parentElement.querySelectorAll(':scope > *');

在撰写此文时,:scope 被 Chrome、Firefox 和 Safari 支持。


:scope 已从规范中移除。我们是否应该删除“未来”部分? - Thomas Marti
不是<style scoped>被从规范中移除了,CanIUse目前报告94%的:scope支持率 - Rafa Viotti

5

除了其他回答之外,这里仍然有值得注意的区别,特别是在处理 <svg> 元素时。

我曾经使用过 .childNodes.children,并且更喜欢使用由 .children 获取器提供的 HTMLCollection

然而今天,在使用 .children 在一个 <svg> 上时,我遇到了IE/Edge失败的问题。 虽然基本的HTML元素中支持 .children但它不支持文档/文档片段或SVG元素

对我来说,我能够简单地通过 .childNodes[n] 获取所需的元素,因为我不需要担心多余的文本节点。你也可以这样做,但就像其他地方提到的一样,不要忘记你可能会遇到意想不到的元素。

希望这对于一些人来说是有用的,他们正在努力弄清楚为什么在现代 IE 上 .children 在其他JS中起作用但在文档或SVG元素上失败。


4
不要让空格欺骗你。在控制台浏览器中测试此功能即可。
使用本地JavaScript。这是一个具有相同类别的两个 'ul' 集合的示例。您不需要将 'ul' 列表全部放在一行中以避免空格,只需使用数组计数来跳过空格即可。
如何通过 querySelector() 然后使用 childNodes[] 来解决空格问题 jsfiddle链接:https://jsfiddle.net/aparadise/56njekdo/
var y = document.querySelector('.list');
var myNode = y.childNodes[11].style.backgroundColor='red';

<ul class="list">
    <li>8</li>
    <li>9</li>
    <li>100</li>
</ul>

<ul class="list">
    <li>ABC</li>
    <li>DEF</li>
    <li>XYZ</li>
</ul>

1
不是我的投票反对,但我认为有人投票反对是因为:a)这个问题已经4年了,当时document.querySelector的支持不太好。b)这个问题基本上是在问childrenchildNodes之间内部的差异,哪一个更可靠,在什么情况下选择其中之一。c)因为,正如问题所示:childNodes包括空格节点,而children不包括... - Elias Van Ootegem

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接