Вы смешиваете несколько понятий здесь.
«Кадр», который вы измеряете в коде, - это один из шагов 10 - Обновление рендеринга .
Цитирую спецификации:
Эта спецификация не требует какой-либо конкретной модели для выбора возможностей рендеринга. Но, например, если браузер пытается достичь частоты обновления 60 Гц, то возможности рендеринга появляются максимум каждые 60 секунд (около 16,7 мс). Если браузер обнаружит, что контекст просмотра не может поддерживать эту скорость, он может упасть до более устойчивых 30 возможностей рендеринга в секунду для этого контекста просмотра, а не время от времени отбрасывать кадры. Точно так же, если контекст просмотра не виден, пользовательский агент может решить отбросить эту страницу до гораздо более медленных 4 возможностей рендеринга в секунду или даже меньше.
Таким образом, неизвестно, на какой частоте будет срабатывать этот * frame , но обычно он работает на частоте 60 кадров в секунду (большинство мониторов обновляется на частоте 60 Гц), поэтому за этот промежуток времени много итераций циклов событий обычно происходит.
Теперь requestAnimationFrame еще более уникален тем, что может отбрасывать кадров , если браузер считает, что он выполняет слишком много задач. Так что ваши фибоначчи, скорее всего, будут задерживать любое выполнение обратных вызовов rAF, пока это не будет сделано.
В статье, о которой вы ссылаетесь, речь идет о « frame » в области PerformanceFrameTiming API . Я должен прямо признать, что у меня мало знаний об этом конкретном API, и, учитывая его очень ограниченную поддержку браузера, я не думаю, что нам следует тратить на него слишком много времени, кроме как сказать, что это не имеет ничего общего с рамкой для покраски.
Я думаю, что наиболее точным инструментом для измерения итерации EventLoop в настоящее время является Messaging API .
Создав цикл вызова сообщения с самозвонком, мы можем подключиться к каждой итерации EventLoop.
let stopped = false;
let eventloops = 0;
onmessage = e => {
if(stopped) {
console.log(`There has been ${eventloops} Event Loops in one anim frame`);
return;
}
eventloops++
postMessage('', '*');
};
requestAnimationFrame(()=> {
// start the message loop
postMessage('', '*');
// stop in one anim frame
requestAnimationFrame(()=> stopped = true);
});
Давайте посмотрим, как ваш код ведет себя на более глубоком уровне:
let done = false;
let started = false;
onmessage = e => {
if (started) {
let a = new Date();
console.log(`new EventLoop - ${a.getHours()}:${a.getMinutes()}:${a.getSeconds()}:${a.getMilliseconds()}`);
}
if (done) return;
postMessage('*', '*');
}
document.getElementById("button").addEventListener("click", handlerOne);
document.getElementById("button").addEventListener("click", handlerTwo);
document.getElementById("button").addEventListener("click", handlerThree);
document.getElementById("button").addEventListener("click", handlerFour);
function handlerOne() {
started = true;
setTimeout(timeoutHandler);
console.log("handler one called. fib(40) = " + fib(40));
}
function handlerTwo() {
console.log("handler two called.");
}
function handlerThree() {
console.log("handler three called.");
}
function handlerFour() {
console.log("handler four called.");
done = true;
}
function timeoutHandler() {
console.log("timeout handler called");
}
function fib(x) {
if (x === 1 || x === 2) return 1
return fib(x - 1) + fib(x - 2);
}
postMessage('*', '*');
<button id="button">Click me</button>
Хорошо, так что на самом деле есть один кадр , как в итерации EventLoop для запуска между обработчиками событий и обратным вызовом setTimeout. Мне нравится больше.
Но как насчет "длинных кадров" , о которых мы слышали?
Полагаю, вы говорите о алгоритме «вращения цикла событий» , который действительно предназначен для того, чтобы цикл событий не блокировал весь пользовательский интерфейс при некоторых обстоятельствах ,
Во-первых, спецификации только говорят разработчикам, что рекомендуется вводить этот алгоритм для долго выполняющихся сценариев, это не обязательно.
Тогда этот алгоритм должен позволить обычную обработку EventLoop регистрации событий и обновлений пользовательского интерфейса, но все, что связано с javascript, просто возобновляется на следующей итерации EventLoop.
Так что на самом деле у js нет способа узнать, вводили ли мы этот алгоритм.
Даже мой управляемый цикл MessageEvent не может сказать, потому что обработчик событий будет просто выдвинут после того, как мы выйдем из этого долго выполняющегося сценария.
Вот попытка изобразить более графически, рискуя быть технически неточным:
/**
* ...
* - handle events
* user-click => push([cb1, cb2, cb3]) to call stack
(* - paint if needed (may execute rAF callbacks if any))
*
* END OF LOOP
—————————————————————————
* BEGIN OF LOOP
*
* - execute call stack
* cb1()
* schedule `timeoutHandler`
* fib()
* ...
* ...
* ...
* ... <-- takes too long => "spin the event loop"
* [ pause call stack ]
* - handle events
(* - paint if needed (but do not execute rAF callbacks))
*
* END OF LOOP
—————————————————————————
* BEGIN OF LOOP
*
* - execute call stack
* [ resume call stack ]
* (*fib()*)
* ...
* ...
* cb2()
* cb3()
* - handle events
* `timeoutHandler` timed out => push to call stack
(* - paint if needed (may execute rAF callbacks if any) )
*
* END OF LOOP
—————————————————————————
* BEGIN OF LOOP
*
* - execute call stack
* `timeoutHandler`()
* - handle events
...
*/