为什么在递归函数中调用getElementsByClassName(cn) 时返回空集合,而在第二次执行时,DOM中已经存在带有cn的新元素?
该脚本正在运行在这篇报纸的页面上:https://www.derstandard.at/story/3000000320803/biobaeuerin-riegler-wir-koennen-nicht-mit-ochsenfuhrwerken-uebers-feld-ziehen,该页面在文章后面有一个留言板。它应该完成两件事:
-
在留言板底部点击按钮 Weitere Postings laden (Load more postings)。这可能会显示:
-
页面底部再出现一个同类按钮
-
更多同类按钮,数量与第2 点所述的相同。
-
如在较长的讨论串末尾出现,点击按钮 Weitere Antworten anzeigen (Show more answers)。这可能会显示更多同类按钮。
-
从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) 是错误的。