为什么在递归函数中调用getElementsByClassName(cn) 时返回空集合,而在第二次执行时,DOM中已经存在带有cn的新元素?

前端开发 2026-07-09

该脚本正在运行在这篇报纸的页面上:https://www.derstandard.at/story/3000000320803/biobaeuerin-riegler-wir-koennen-nicht-mit-ochsenfuhrwerken-uebers-feld-ziehen,该页面在文章后面有一个留言板。它应该完成两件事:

  1. 在留言板底部点击按钮 Weitere Postings laden (Load more postings)。这可能会显示:

  2. 页面底部再出现一个同类按钮

  3. 更多同类按钮,数量与第2 点所述的相同。

  4. 如在较长的讨论串末尾出现,点击按钮 Weitere Antworten anzeigen (Show more answers)。这可能会显示更多同类按钮。

  5. 从1 开始重复,直到按钮的 getElementsByClassName('thread--more').length == 0 通过相应的函数递归调用处理完毕。

一开始函数执行时一切正常。到了第二次执行时,getElementsByClassName('thread--more').length == 0,页面/DOM上却存在属于第二类的新按钮(通过 Ctrl+F 和FF的 DOM Inspector进行检查)。

MCVE

// ==UserScript==
// @name         derStandard Postings
// @version      20260519-1956
// @icon         https://b.staticfiles.at/s/icons/nachrichten/favicon-16x16.png
// @author       derStandard-Forum
// @ ---------------------------------------------------------------------------
// @namespace    dst
// @match        https://www.derstandard.at/*/*
// @match        https://www.derstandard.de/*/*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function() {
  'use strict';

  expandAllThreads()

  function expandAllThreads() {

    try {
      console.info("BEGIN derStandard Postings - expandAllThreads()...")

      // give the page time to load its content
      setTimeout(() => {
        console.info("BEGIN derStandard Postings - expandAllThreads()...delayed...")

        const forum = document.querySelector('dst-forum').shadowRoot.firstElementChild
        console.debug("forum:", forum)

        clickMore(forum)

        console.info("END derStandard Postings- expandAllThreads()...delayed.")
      }, 5000) // increase if time isn't enough to load page content completely

      console.info("END derStandard Postings- expandAllThreads().")
    } catch (e) {
      console.error(e)
    }

  } // expandAllThreaads()

  function clickMore(forum) {
    console.debug("clickMore()...")

    const btns = forum.getElementsByClassName('thread--more')
    console.debug("before click btns:", btns)

    if (btns.length > 0) {
      while (btns.length > 0) {
        btns[0].addEventListener('click', function() {
          console.debug("clicked btn:", btns[0].innerText, "[0]", "of:", btns)
        })
        btns[0].click()
        btns[0].remove()
      } // while ( btns exist )
      console.debug("after click btns:", btns)
      clickMore(forum)
    } // if ( btns exist )

  } // clickMore()

})();

FF控制台输出(已移除文件信息)

BEGIN derStandard Postings - expandAllThreads()...
END derStandard Postings- expandAllThreads().
BEGIN derStandard Postings - expandAllThreads()...delayed...
> forum: 
  <section id="forum" class="forum" aria-labelledby="forum--title" aria-describedby="forum--description" tabindex="-1">
> clickMore()...
> before click btns:
  HTMLCollection { 0: button.thread--more.form--button.form--button-weak.form--button-small, 1: button.thread--more.form--button.form--button-weak.form--button-small, 2: button.form--button.thread--more, length: 3 }
> clicked btn: Weitere Antworten anzeigen [0] of: 
  HTMLCollection { 0: button.thread--more.form--button.form--button-weak.form--button-small, 1: button.thread--more.form--button.form--button-weak.form--button-small, 2: button.form--button.thread--more, length: 3 }
> clicked btn: Weitere Antworten anzeigen [0] of: 
  HTMLCollection { 0: button.thread--more.form--button.form--button-weak.form--button-small, 1: button.form--button.thread--more, length: 2 }
> clicked btn: Weitere Postings laden [0] of: 
  HTMLCollection { 0: button.form--button.thread--more, length: 1 }
> after click btns: 
  HTMLCollection { length: 0 }
> clickMore()...
> before click btns: 
  HTMLCollection { length: 0 }
END derStandard Postings- expandAllThreads()...delayed.

更新

即便如同两条回答所述,将 clickMore() 的最后一行改为:

setTimeout(clickMore( forum ), 10000)

输出仍然是:

...
04:12:06.611 after click btns: HTMLCollection []
04:12:06.611 clickMore()...
04:12:06.612 before click btns: HTMLCollection []

嵌套的 setTimeout() 不起作用吗?

解决方案

按钮是异步添加的,但你在第一次运行后立即调用 clickMore,这会导致找不到新按钮,在第二次调用时结束递归。

要解决它,你可以像另一条回答所建议的那样添加一个超时,或者采用另一种方法;使用 MutationObserver

// ==UserScript==
// @name         derstandard
// @namespace    http://tampermonkey.net/
// @version      2026-05-20
// @description  show all posts
// @author       You
// @match        https://www.derstandard.at/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=derstandard.at
// @grant        none
// ==/UserScript==

function observeButtons(section) {
    const observer = new MutationObserver( () => {
        const buttons = section.querySelectorAll("button.thread--more");
        buttons.forEach(btn => btn.click());
    })

    observer.observe(section, { childList: true, subtree: true })
}


const containerObserver = new MutationObserver( () => {
    const dst = document.querySelector("dst-forum[contentid]");
    if (!dst) return;

    containerObserver.disconnect();
    const section = dst.shadowRoot.querySelector("section#forum");
    observeButtons(section);
});

const container = document.querySelector("div.story-community-postings");
containerObserver.observe(container, { childList: true, subtree: true })

要使用 setTimeout 修复你现有的代码,需要将递归调用 clickMore(forum) 替换为以下任意一种:

setTimeout(clickMore, 1000, forum);

或者这样:

setTimeout(() => clickMore(forum), 1000);

因为这种语法:setTimeout(clickMore( forum ), 10000) 是错误的。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章