JavaScript中触发点击事件后如何等待DOM加载渲染完成?
问题场景
我正在编写一个数据爬取脚本。页面包含一个列表,同一时间仅支持展开单个列表项,此处的「展开」指点击对应行后,在所选行下方渲染一个div元素,用于展示点击触发拉取的相关数据。
初始实现代码如下:
function findData(arr, matches_regex) { return Array.prototype.slice .call(arr) .find((x) => matches_regex(x.textContent))?.textContent; } rows = document.getElementsByClassName("selectable-row") data = [] for (var i = 0; i < rows.length; i++) { rows[i].click() row_data = document.getElementsByClassName("row-data") data1 = findData(row_data, matches_regex_1) data2 = findData(row_data, matches_regex_2) data.push([data1, data2]) }
现存异常:执行rows[i].click()后,后续脚本逻辑会在DOM完成拉取数据的渲染前提前执行,导致本应获取到有效数据的data1、data2变量返回undefined。需要实现点击操作后等待DOM完全加载渲染完成,再执行后续数据获取逻辑。
可行方案
核心原因是click()触发的数据拉取、DOM渲染都是异步操作,原生同步for循环不会等待异步任务执行完成,会直接往下运行取值逻辑,自然拿到空值。
方案1:MutationObserver 监听DOM变化(推荐,稳定性最高)
该方案不需要硬编码等待时长,会自动监听目标DOM的变化,只要.row-data元素渲染完成、内容填充完毕就触发后续取值逻辑,适配不同网络速度场景,不会出现等待时长不足或过长的问题。
改造后完整代码:
function findData(arr, matches_regex) { return Array.prototype.slice .call(arr) .find((x) => matches_regex(x.textContent))?.textContent; } // 等待行数据渲染完成的工具函数 function waitForRowData(timeout = 5000) { return new Promise((resolve, reject) => { // 先检查一次有没有已经渲染好的内容,避免重复监听 const existData = document.querySelector(".row-data"); if (existData && existData.textContent.trim()) { return resolve(existData); } const observer = new MutationObserver(() => { const currentData = document.querySelector(".row-data"); // 检测到.row-data存在且有实际内容就停止监听返回结果 if (currentData && currentData.textContent.trim()) { observer.disconnect(); resolve(currentData); } }); // 监听整个文档的子节点变化、内容变化 observer.observe(document.body, { childList: true, subtree: true, characterData: true }); // 加超时避免异常场景下无限等待 setTimeout(() => { observer.disconnect(); reject(new Error("行数据加载超时")); }, timeout); }); } async function fetchAllData() { const rows = document.getElementsByClassName("selectable-row"); const data = []; for (let i = 0; i < rows.length; i++) { rows[i].click(); // 等待当前行数据渲染完成再取值 await waitForRowData(); const row_data = document.getElementsByClassName("row-data"); const data1 = findData(row_data, matches_regex_1); const data2 = findData(row_data, matches_regex_2); data.push([data1, data2]); } return data; } // 执行脚本,拿到结果后可自行处理 const allData = await fetchAllData(); console.log(allData);
方案2:短间隔轮询(临时跑脚本可用,实现简单)
如果不想写Observer逻辑,可以用固定短间隔轮询检测DOM状态,实现更简单,适合临时一次性跑脚本的场景,效果优于写死长延时。
只需要把上面代码里的waitForRowData替换成下面的实现即可:
function waitForRowData(timeout = 5000, interval = 50) { return new Promise((resolve, reject) => { const check = () => { const currentData = document.querySelector(".row-data"); if (currentData && currentData.textContent.trim()) { return resolve(currentData); } timeout -= interval; if (timeout <= 0) { return reject(new Error("行数据加载超时")); } setTimeout(check, interval); }; check(); }); }
注意:不要直接使用固定时长的
setTimeout等待(比如点击后固定等1000ms再取值),网络较差时会因为等待时长不足拿不到数据,网络较好时会无谓增加脚本运行时长,稳定性极差。
内容的提问来源于stack exchange,提问作者Timr11
相关产品推荐
相关产品推荐

