I have been reading a lot about the event loop lately and the different queues (job/macrotask queue, microstask queue, render queue) it has and their priority. But am still not able to fully understand one thing, let's say a user fires 2 events one after the other pretty fast. Then both event handlers will be enqueued on the job queue and executed in the correct order. But what if the first event handler does some heavy computations and blocks the main thread for a while but it also manipulates the DOM in a way that changes the UI so that the element that the user clicked on the second time is removed and replaced by some other dom element that also has a click event registered.
Then if I have understood the process correctly, once the first event handler finishes execution, all the microtasks get executed and the microtask queue is emptied. Then the render queue gets emptied and the browser repaints. Here is where my actual question comes, does a second event handler get executed based on the new state of the UI, or based on the element which was there at the time of the click?
I tried this out (https://jsfiddle.net/jsx73t6c/4/) by having 3 buttons each with a click event handler registered to it that logs to the console that the respective button was clicked. I click fast on button 1 and button 2. Button 1's handler does a big loop so the main thread is blocked for a while and then removes button 2. And since button 2 is removed on it's place comes button 3 and I end up with 2 console logs indicating that button 1 and button 3 were clicked even though at the time when my mouse is clicked it was on button 1 and button 2. It seems like the event handlers are registered on the queue but the elements that click happens on are determined later when it's time to process the click event and trigger the event handlers that are registered. To me this behavior makes more sense and is a more predictable behavior than having the clicked elements determined at the time of the click based on the state of the DOM then. But I am still not sure if the behavior in my example is always guaranteed.
const button2 = getButton(2);
const button3 = getButton(3);
const removeButton2 = () => button2.parentElement.removeChild(button2);
button2.addEventListener("click", () => console.log("button 2 clicked"));
button3.addEventListener("click", () => console.log("button 3 clicked"));
button1.addEventListener("click", () => {
console.log("button 1 clicked");
const fragment = document.createDocumentFragment();
for (let i = 0; i < 70_000; i++) {
const a = document.createElement("a");
a.innerHTML = "text " + i;
fragment.appendChild(a);
}
document.documentElement.appendChild(fragment);
removeButton2();
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button id="button-1">button 1</button>
<button id="button-2">button 2</button>
<button id="button-3">button 3</button>
</body>
</html>
I know that such real-world scenario is highly unlikely to stumble upon with the current state of browsers where everything happens so fast, but would appreciate some more light and resources on the topic.