有时候,您需要操作尚未存在的 DOM 的某个部分。
blur事件的响应并没有得到工具的正式支持,所以我打算自己来设计一个事件监听器。然而,通过像.querySelector(
这样的方法来尝试访问节点会返回null
,因为此时节点还没有被浏览器渲染,并且我也不知道究竟什么时候会被渲染。
// Simulating lazily-rendered HTML:
setTimeout(( => {
const button = document.createElement('button';
button.id = 'button';
button.innerText = 'Do Something!';
document.body.append(button;
}, randomBetweenMs(1000, 5000;
document.querySelector('#button'.addEventListener('click', ( => {
alert('clicked!'
};
// Error: Cannot read properties of null (reading 'addEventListener'
真的是毫无意外。你看到的所有代码都会被丢进调用栈并立即执行(当然,除了setTimeout
的回调函数),所以当我试图访问按钮时,我所得到的便是null
。
轮询
setInterval或者setTimeout
这样的方法,下面是使用递归的例子:
function attachListenerToButton( {
let button = document.getElementById('button';
if (button {
button.addEventListener('click', ( => alert('clicked!';
return;
}
// If the node doesn't exist yet, try
// again on the next turn of the event loop.
setTimeout(attachListenerToButton;
}
attachListenerToButton(;
或者,你可能已经见过一种基于Promise的方法,这感觉更现代一些:
async function attachListenerToButton( {
let button = document.getElementById('button';
while (!button {
// If the node doesn't exist yet, try
// again on the next turn of the event loop.
button = document.getElementById('button';
await new Promise((resolve => setTimeout(resolve;
}
button.addEventListener('click', ( => alert('clicked!';
}
attachListenerToButton(;
不管怎么说,这种策略都有非同小可的代价--主要是性能。在这两个版本中,移除setTimeout(
会导致脚本完全同步运行,阻塞主线程,以及其他需要在主线程上进行的任务。没有输入事件会被处理。你的标签会被冻结。混乱不会随之而来。
setTimeout((或者setInterval
),将下一次尝试推迟到到事件循环的下一个迭代中,这样就可以在这期间执行其他任务。但你仍然在重复地占用调用栈,等待你的节点出现。如果你想让你的代码很好地管理事件循环,那这就太不理想了。
click事件监听器,你不希望用户在几毫秒后才附加监听器之前就有机会点击该元素。这样的问题可能很少见,但当你稍后调试可能出错的代码时,它们肯定会带来烦恼。
MutationObserver(
body 内部任何变化的基本设置如下所示:
const domObserver = new MutationObserver((mutationList => {
// document.body has changed! Do something.
};
domObserver.observe(document.body, { childList: true, subtree: true };
对于我们构造的示例,进一步完善也相当简单。每当树发生变化时,我们将查询特定的节点。如果节点存在,则附加监听器。
const domObserver = new MutationObserver(( => {
const button = document.getElementById('button';
if (button {
button.addEventListener('click', ( => alert('clicked!';
}
};
domObserver.observe(document.body, { childList: true, subtree: true };
我们传递给 .observe(
的选项很重要。将 childList
设置为 true
使观