我正在进行实验以更好地了解JavaScript事件队列。
我有一个昂贵的操作,什么也不做,我只是用来占用主线程。持续约400毫秒。
然后我有一个带有keypress事件侦听器和onChange处理程序的输入,只要在输入中按下一个键,onChange处理程序就会运行此昂贵的操作。但是在执行昂贵的操作之前,它会设置setTimeout的延迟为0,并且可以跟踪一些回调。
我要做的是快速连续按下两个键。检查性能配置文件,我可以看到在第一个按键处理程序开始执行大约150毫秒后,第二个按键被注册。这意味着延迟为0的setTimeout回调应该已经完成其计时器并将其回调放入事件队列中。 keypress事件还应该将其处理程序回调放置在队列中,因为注册第二个keypress时,主线程仍在忙于第一个昂贵的操作。
因此,您可能希望,如果将超时回调放在第二个keypress事件之前首先放入事件队列中,则根据主线程的说法,一旦主线程完成了昂贵的操作,超时回调将首先运行。先进先出任务队列原则。
情况并非如此。按键事件排在第一位...
我正试图了解原因。我的猜测是不仅只有一个事件队列。诸如按键之类的用户事件具有与计时器事件不同的队列,并且JavaScript引擎优先于计时器来优先于用户事件。
这是一个复制此方案的codeSandbox,因此您可以自己测试结果。该代码也写在下面:https://codesandbox.io/s/r6vzp1qyq
你们认为发生了什么事?
function App() {
const [timeoutTimestamps, setTimeoutTimestamp] = useState([]);
const [keypressTimeStamps, setKeypressTimeStamp] = useState([]);
//This expensive operation takes about 400 ms.
function expensiveOperation() {
const arr = new Array(30000000);
arr.map(() => {
arr.forEach(() => {
arr.forEach(() => {});
});
});
}
function timerCallback() {
const newTimestamp = Date.now();
setTimeoutTimestamp(prevState => [...prevState, newTimestamp]);
}
function onKeypress() {
const newTimestamp = Date.now();
setTimeout(timerCallback, 0);
expensiveOperation();
setKeypressTimeStamp(prevState => [...prevState, newTimestamp]);
}
return (
<div className="App">
<input onChange={onKeypress} />
<div className="container">
<div className="time-stamps">
<h3>Keypress TimeStamps</h3>
{keypressTimeStamps.map(time => (
<span>{time - keypressTimeStamps[0]}</span>
))}
</div>
<div className="time-stamps">
<h3>Timeout TimeStamps</h3>
{timeoutTimestamps.map(time => (
<span>{time - keypressTimeStamps[0]}</span>
))}
</div>
</div>
</div>
);
}
您可以看到首先触发了第二个按键回调,然后触发了两个超时回调。