Таймер не вовремя ☞Позвольте вам раскрыть секреты setTimeout и setInterval

JavaScript
Таймер не вовремя ☞Позвольте вам раскрыть секреты setTimeout и setInterval

1. Мысли, вызванные вопросом на собеседовании

Однажды на работе кто-то из Q-группы отправлял письменные тестовые вопросы в онлайн-справку. Наверное посмотрел. Найдено, что есть вопрос субъективного суждения.

в кодеsetInterval(()=>{console.log('a')},10000), который будет выводить a на консоль каждые 10 секунд.

可能很多人第一印象,包括我再内,都认为这道题是对的。但是其实是错的! !

Почему механизмы исполнения JavaScript делают призрак, то каковы механизмы реализации JavaScript, не могут понять точкуздесьпосмотри.

Во-вторых, определение и использование setTimeout

1. Определение setTimeout

setTimeout()Метод используется для вызова функции или вычисления выражения через указанное количество миллисекунд.

2. Параметры setTimeout

  • Функция первого параметра, необходимая функция обратного вызова может быть функцией, это может быть имя функции.

  • Второй параметр задержка, необязательный, время задержки, единица измерения мс.

  • Третий параметр param1, param2, param3..., необязательный, — это параметр, передаваемый функции обратного вызова, который обычно не используется, и этот параметр не поддерживается в IE9 и более ранних версиях.

    setTimeout(function(a) {
    	console.log(a);
    }, 2000,'我是定时器')
    
    setTimeout(foo, 2000,'我是定时器')
    function foo(a){
        console.log(a)
    }
    

3. Возвращаемое значение setTimeout

Возвращает идентификатор (число), которое можно передатьclearTimeout()отменить выполнение.

3. Определение и использование setInterval

1. Определение setinterval

setInterval()Метод вызывает функцию или оценивает выражение в указанный период времени (в миллисекундах).

2. Параметры setInterval

  • Первая необходимая функция параметра, функция обратного вызова, может быть функцией или именем функции.
  • Второй параметр задержка, необязательный, интервал времени, единица измерения мс.
  • Третий параметр param1, param2, param3..., необязательный, — это параметр, передаваемый функции обратного вызова, который обычно не используется, и этот параметр не поддерживается в IE9 и более ранних версиях.
    setInterval(function(a) {
    	console.log(a);
    }, 2000,'我是定时器')
    
    setInterval(foo, 2000,'我是定时器')
    function foo(a){
        console.log(a)
    }
    

Возвращает идентификатор (число), которое можно передатьclearInterval()отменить исполнение.

В-четвертых, минимальное время задержки setTimeout

Когда второй параметр delay не установлен, по умолчанию он равен 0, что означает выполнение «немедленно» или выполнение как можно скорее.

Но есть такая оговорка

If timeout is less than 0, then set timeout to 0. If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4.

Вышеприведенное означает, что если время задержки меньше 0, установите время задержки равным 0. Если уровень вложенности больше 5 и время задержки меньше 4 мс, установите время задержки на 4 мс.

Есть другая ситуация. В целях экономии энергии браузер расширит минимальный предел задержки до 1000 мс для тех страниц, которые не находятся в текущем окне.

Можно сказать, что вышеизложенное является одной из причин того, что таймер не вовремя.

Пять, кратчайшее время интервала setInterval

Упоминается в новой книге Джона Резига "Secrets of the Javascript Ninja".

Browsers all have a 10ms minimum delay on OSX and a(approximately) 15ms delay on Windows.

Минимальное время интервала на Mac составляет 10 миллисекунд, а минимальное время интервала в системах Windows — около 15 миллисекунд.

Большинство компьютерных мониторов имеют частоту обновления 60 Гц, что соответствует примерно 60 перерисовкам в секунду. Поэтому оптимальный интервал цикла для наиболее плавного эффекта анимации составляет 1000 мс/60, что примерно равно 16,6 мс.

Подводя итог, я думаю, что минимальное время интервала для setInterval должно быть 16,6 мс.

В-шестых, не вовремя SETTIMEOUT и SETINTERVAL

Независимо от того, в каком случае фактическое время задержки может быть больше, чем ожидаемое значение (DELY миллисекунды).

В дополнение к установленному DELAY короче, чем кратчайшее время задержки и кратчайший интервал, есть причина, по которой механизм выполнения JavaScript вызван примером.

<body>
    <button id="btn"></button>
    <script>
        const btn = document.getElementById("btn");
        btn.addEventListener('click',function handleClick(){
            //...代码执行时间需80ms
        })
    	setTimeout(function handlerTimeout(){
            //...代码执行时间需60ms
        }, 100);
        setInterval(function handlerInterval(){
            //...代码执行时间需80ms
        },100)
        //... 其余代码执行时间需要180ms
    </script>
</body>

Мы используем временную шкалу, чтобы описать, как этот код выполняется.

В 100 мс два таймера изначально завершались одновременно, но таймер setTimeout был записан впереди, поэтому его функция обратного вызова handlerTimeout сначала попала в очередь событий и выполнилась первой. Функция обратного вызова handlerInterval выполняется после входа в очередь событий.

Но реальная ситуация такова, что, поскольку время выполнения остального кода занимает 180 мс, то есть основной поток должен бездействовать до 180 мс, поэтому функция обратного вызова handlerTimeout может быть выполнена только тогда, когда оно составляет 180 мс. Функция обратного вызова handlerInterval должна дождаться выполнения функции обратного вызова handlerTimeout, прежде чем ее можно будет выполнить, что эквивалентно 240 мс.

Почему происходит вышеуказанное явление, настоящая причина может перейти в другую мою статьюКак именно выполняется JavaScript 🔥

Таким образом, можно сделать вывод, что setTimeout и setInterval не могут гарантировать, что callback-функция будет выполнена вовремя.

Семь, заброшенная функция обратного вызова setInterval

Через 200 мс снова выполняется setInterval, и функция обратного вызова handlerInterval снова войдет в очередь событий.

Ответ — нет, потому что в это время в очереди событий уже есть callback-функция handlerInterval.

Функция обратного вызова setInterval теперь устарела.

Восемь, время выполнения функции обратного вызова setInterval

В 240 мс заканчивается выполнение функции обратного вызова handlerTimeout и выполняется функция обратного вызова handlerInterval.

Через 300 мс снова выполняется setInterval, и обнаруживается, что в очереди событий нет callback-функции handlerInterval. В это время функция обратного вызова handlerInterval войдет в очередь событий.

В 320 мс завершается выполнение последней функции обратного вызова handlerTimeout, и немедленно выполняется следующая функция обратного вызова handlerTimeout.

На 400 мс снова выполняется setInterval, и обнаруживается, что в очереди событий нет callback-функции handlerInterval. В это время функция обратного вызова handlerInterval войдет в очередь событий. Бывает, что выполнение последней callback-функции handlerTimeout завершается, а следующая callback-функция handlerTimeout выполняется сразу.

В это время вы обнаружите, что функция обратного вызова handlerTimeout выполняется без интервала, и интервал отсутствует.

Следовательно, интервал времени setInterval должен быть больше, чем время выполнения функции обратного вызова.

Однако во многих случаях мы не знаем точно время выполнения callback-функции, поэтому для периодического срабатывания таймера через определенный интервал можно использовать следующие методы.

setTimeout(function handlerInterval(){
    // do something
    setTimeout(handlerInterval,100); 
    // 执行完处理程序的内容后,在末尾再间隔100毫秒来调用该程序,这样就能保证一定是100毫秒的周期调用
},100)

Однако этот метод имеет временную ошибку, поэтому нам необходимо его оптимизировать.

function mySetInterval(timeout) {
    const startTime = new Date().getTime();
    let countIndex = 1;
    let onOff=true;
    startSetInterval(timeout)
    function startSetInterval(interval) {
        setTimeout(() => {
            const endTime = new Date().getTime();
            // 偏差值
            const deviation = endTime - (startTime + countIndex * timeout);
            console.log(`${countIndex}: 偏差${deviation}ms`);
            countIndex++;
            // 下一次
            if(onOff){
                startSetInterval(timeout - deviation);
            }
        }, interval);
    }
    
    function stopSetInterval(){
        onOff=false;
    }
    return stopSetInterval
}