Я провожу эксперимент, чтобы лучше понять очередь событий JavaScript.
У меня дорогая операция, которая ничего не делает, которую я просто использую, чтобы занять основной поток.Это длится около 400 мс.
Затем у меня есть вход с прослушивателем события нажатия клавиши и обработчиком onChange, который выполняет эту дорогостоящую операцию всякий раз, когда на входе нажимается клавиша.Но перед запуском дорогостоящей операции он устанавливает setTimeout с задержкой 0 с некоторым обратным вызовом, который я мог отследить.
Я быстро нажал две клавиши подряд.Проверяя профиль производительности, я увидел, что второе нажатие клавиши регистрируется примерно через 150 мс после того, как первый обработчик нажатия клавиш начал выполнение.Это означает, что обратный вызов setTimeout с задержкой 0 должен завершить таймер и поместить обратный вызов в очередь событий.Событие нажатия клавиши также должно поместить свой обратный вызов обработчика в очередь, потому что основной поток все еще занят первой дорогой операцией, когда было зарегистрировано второе нажатие клавиши.
Таким образом, вы ожидаете, что, если обратный вызов времени ожидания был помещенсначала в очередь событий до 2-го события нажатия клавиши, что обратный вызов тайм-аута будет запущен первым, как только основной поток завершит дорогостоящую операцию, в соответствии с принципом очереди задач First In First Out.
Это не тот случай.Сначала происходит событие нажатия клавиши ...
Я пытаюсь понять, почему.Я предполагаю, что не существует только одной очереди событий.Пользовательские события, такие как нажатия клавиш, имеют отдельную очередь от событий таймера, и механизм JavaScript отдает приоритет пользовательским событиям над таймерами.
Вот кодовый ящик с этим сценарием, который реплицируется, так что вы можете проверить результаты самостоятельно.Код также написан ниже: 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>
);
}
Этот код отображает следующие метки времени:
Вы видите, что сначала срабатывает обратный вызов 2-го нажатия клавиш, затем срабатывает обратный вызов 2-го тайм-аута.