我对以下代码片段的行为感到困惑。我有一个按钮,在单击后运行长时间计算时会被禁用。
function delay() {
console.log('start');
for (let x = 0; x < 4000000000; x++) {
Math.sqrt(20);
}
console.log('done');
}
function click() {
button.disabled = true;
setTimeout(() => {
delay();
button.disabled = false;
}, 0);
}
const button = document.getElementById('button');
button.addEventListener('click', click);
按钮成功禁用并且运行了很长的计算,但是发生了一些奇怪的事情:如果我在按钮处于禁用状态时多次单击按钮,它会将单个单击事件排队并在完成后再次运行 click() 函数当前计算。
我的理解是禁用的元素不应触发或排队任何事件。谁能解释一下这种行为吗?
我已经阅读了 JavaScript 的微任务和宏任务以及 UI 如何在它们之间更新。基于此,我预计按钮在禁用时对点击完全没有响应。
但是,我观察到,尽管按钮被禁用,但如果我在禁用按钮时多次单击它,单个单击事件仍然会排队。
它与浏览器如何处理快速事件序列和元素的禁用状态有关。
当您快速单击该按钮时,即使该按钮被禁用,由于浏览器快速连续处理事件的方式,浏览器仍可能会注册单击事件。如果按钮被禁用然后在短时间内重新启用(就像您的代码一样),则尤其如此。
以下是所发生事件的详细说明:
您单击按钮。 调用点击函数。 该按钮立即被禁用。 setTimeout 函数安排延迟函数在当前调用堆栈被清除后运行。
当按钮被禁用时,您再次单击它。由于事件发生的速度很快,浏览器可能仍会注册此单击事件。 延迟功能运行,完成后按钮重新启用。 即使在按钮禁用时注册了排队的单击事件,也会处理该事件。 为了避免这种行为,您可以使用标志来检查计算是否已经在运行并防止再次调用该函数:
let isRunning = false;
function delay() {
console.log('start');
for (let x = 0; x < 4000000000; x++) {
Math.sqrt(20);
}
console.log('done');
}
function click() {
if (isRunning) return;
isRunning = true;
button.disabled = true;
setTimeout(() => {
delay();
button.disabled = false;
isRunning = false;
}, 0);
}
const button = document.getElementById('button');
button.addEventListener('click', click);
通过此修改,即使浏览器在按钮禁用时注册了单击事件,单击函数也会立即返回而不执行任何操作,因为 isRunning 标志设置为 true。