You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Chrome 96环境下Element.getElementsByClassName返回节点数不一致问题咨询

环境信息

  • Windows 10,Chrome 96.0.4664.45(正式版 64位)
  • 该问题在Firefox 94.0.1(64位)版本中不会复现

问题描述

我在使用Element.getElementsByClassName时遇到了不符合预期的行为,已附上完整可复现问题的HTML文件内容,控制台输出如下:

this.node.getElementsByClassName("divIndicator").length = 0

this.node.getElementsByClassName("indicators")[0].getElementsByClassName("divIndicator").length = 3
this.node.tBodies[0].getElementsByClassName("divIndicator").length = 3
this.node.parentNode.getElementsByClassName("divIndicator").length = 3

我创建了一个JS对象维护组件的根DOM节点引用,从该根DOM节点调用getElementsByClassName时,浏览器无法获取类名为divIndicator的节点。
但从该根节点的子节点(或父节点)调用getElementsByClassName时,方法可以正确返回3个类名为divIndicator的DIV元素。
请问我的代码哪里存在问题?

<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript">
            window.onload = main
            function main() {
                var nav = new Navigation()
                document.body.appendChild(nav.node)
                nav.selectNavItem(0)
            }

            //Navigation更类似选项卡面板或选项卡容器
            class Navigation {
                constructor() {
                    this.node = this.initDom()
                    this.node.model = this

                    this.contentContainers = []
                }

                initDom() {
                    //创建DOM树
                    this.node = this.template()

                    this.initNavItems()

                    return this.node
                }

                initNavItems() {
                    //关闭指示器
                    var indicators = this.node.getElementsByClassName("divIndicator")
                    for (var indicator of indicators) {
                        indicator.style.display = "none"
                    }
                    indicators[0].style.display = "initial"

                    //为导航项绑定监听器
                    var navItems = this.node.getElementsByClassName("labels")[0].getElementsByTagName("td")
                    for (var i=0; i < navItems.length; i++) {
                        navItems[i].addEventListener("click", function(self, idx) {
                            return function(evt) {
                                self.selectNavItem(idx)
                            }
                        } (this, i))
                    }
                }

                selectNavItem(idx, evt) {
                    //设置导航和内容容器的状态(遵循选项卡面板模式)
                    var indicators = this.node.getElementsByClassName("divIndicator")
                    console.log('this.node.getElementsByClassName("divIndicator").length = ' + indicators.length)

                    indicators = this.node.getElementsByClassName("indicators")[0].getElementsByClassName("divIndicator")
                    console.log('this.node.getElementsByClassName("indicators")[0].getElementsByClassName("divIndicator").length = ' + indicators.length)

                    indicators = this.node.tBodies[0].getElementsByClassName("divIndicator")
                    console.log('this.node.tBodies[0].getElementsByClassName("divIndicator").length = ' + indicators.length)

                    indicators = this.node.parentNode.getElementsByClassName("divIndicator")
                    console.log('this.node.parentNode.getElementsByClassName("divIndicator").length = ' + indicators.length)

                    if (indicators.length == 0) {
                        console.error("使用替代选择器获取divIndicator元素")
                        indicators = this.node.tBodies[0].getElementsByClassName("divIndicator")
                    }

                    for (var i=0; i < indicators.length; i++) {
                        if (i == idx) {
                            //显示下箭头指示器
                            indicators[idx].style.display = "block"

                            //显示对应内容容器
                            if (this.contentContainers[idx])
                                this.contentContainers[idx].node.style.display = "block"
                        } else {
                            //隐藏下箭头指示器
                            indicators[i].style.display = "none"

                            //隐藏对应内容容器
                            if (this.contentContainers[i])
                                this.contentContainers[i].node.style.display = "none"
                        }
                    }
                }

                addContentContainer(cc) {
                    this.contentContainers.push(cc)
                }

                template() {
                    return new DOMParser().parseFromString(`
                        <table id="tblNavigation" class="navigation font4" width="540" height="100">
                            <tr class="labels">
                                <td>餐饮</td>
                                <td>食物</td>
                                <td>食谱</td>
                            </tr>
                            <tr>
                                <td colspan="3">
                                    <div style="height: 2px; background-color: white;">
                                </td>
                            </tr>
                            <tr class="indicators">
                                <td><div class="divIndicator"><span>ᐯ</span></div></td>
                                <td><div class="divIndicator"><span>ᐯ</span></div></td>
                                <td><div class="divIndicator"><span>ᐯ</span></div></td>
                            </tr>
                        </table>
                    `, "text/html").body.firstChild
                }
            }
        </script>
    </head>
    <body>
    </body>
</html>

更新1

参考Randy Casburn的回答,进一步定位后发现,Chrome调试器显示该异常行为在调用document.body.appendChild()后立即出现,且仅当initNavItems()方法被调用且方法体内至少有一条语句时才会触发。对原initDom()方法做了小幅修改后问题似乎得到了解决,但暂不清楚背后的原因。

原initDom()方法:

initDom() {
    //创建DOM树
    this.node = this.template()

    this.initNavItems()

    return this.node
}

修改后:

initDom() {
    //创建DOM树
    this.node = this.template()

    var self = this
    setTimeout(function() { self.initNavItems() }, 0)

    return this.node
}

更新2

最终选择放弃使用DOMParser从字符串创建DOM元素的方案,目前至少有3种其他实现方案,最终选择使用<template>标签实现,修改后的template()函数如下:

template() {
    var template = document.createElement("template")
    template.innerHTML = `
        <div class="divContentContainer" style="display: none;">
        ...其他HTML内容...
    </div>
    `
    return template.content.firstElementChild
}

内容的提问来源于stack exchange,提问作者hotfix_cowboy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 20:15:03