предисловие
Таймер повторения, в JS есть метод setInterval, специально созданный для этого, но все недовольны им по многим причинам, таким как пропуск кадров, например, легкая утечка памяти, это дитя, которое никто не любит. Более того, setTimeout может полностью добиться эффекта повторного тайминга за счет собственной итерации, поэтому setIntervval более незаинтересован, и использовать setInterval очень низко. Но! Действительно ли setInverval уступает setTimeout? Пожалуйста, следуйте за автором, чтобы изучить шаг за шагом!
контур
-
Проблемы с повторяющимися таймерами
-
Рукописный повторяющийся таймер
- Проблемы и оптимизация setTimeout
- Проблемы и оптимизация setInterval
-
Горшок с setInterval в те годы - легко вызвать утечку памяти
Различные проблемы с повторяющимися таймерами
Ни setTimeout, ни setInterval не могут избежать проблемы задержки выполнения и пропуска кадров. Зачем? Причина в том, что стек JS в цикле событий слишком занят, когда наступает очередь таймера выполнять обратный вызов, он уже истек. Другая причина в том, что callback-операция самого таймера слишком тяжелая, да еще и имеет асинхронные операции, так что невозможно оценить время работы и установить время.
статьи setTimeout
setTimeout эти вещи
Автор впервые узнал об использовании setTimeout через собственную итерацию для достижения эффекта повторяющегося тайминга.
setTimeout(function(){
var div = document.getElementById("myDiv");
left = parseInt(div.style.left) + 5;
div.style.left = left + "px";
if (left < 200){
setTimeout(arguments.callee, 50);
}
}, 50);
выбран изРасширенное программирование с помощью JavaScript (3-е издание)страница 611
Это должен быть очень классический способ написания, но сам setTimeout требует дополнительного времени для запуска и активации следующего запуска после окончания выполнения. Это вызовет проблему постоянной задержки времени.Первоначальный интервал составляет 1000 мс, а неосознанная задержка setTimeout может постепенно увеличиваться до отклонения в 2000 мс в общей продолжительности.
Исправить ограничение setTimeout
Когда дело доходит до желания исправить смещение времени, о чем вы думаете? Вот так! Это операция получения текущего времени, С помощью этой операции мы можем восстанавливать интервал времени каждый раз, когда мы запускаем, чтобы общее время не отклонялось слишком сильно.
/*
id:定时器id,自定义
aminTime:执行间隔时间
callback:定时执行的函数,返回callback(id,runtime),id是定时器的时间,runtime是当前运行的时间
maxTime:定时器重复执行的最大时长
afterTimeUp:定时器超时之后的回调函数,返回afterTimeUp(id,usedTime,countTimes),id是定时器的时间,usedTime是定时器执行的总时间,countTimes是当前定时器运行的回调次数
*/
function runTimer(id,aminTime,callback,maxTime,afterTimeUp){
//....
let startTime=0//记录开始时间
function getTime(){//获取当前时间
return new Date().getTime();
}
/*
diffTime:需要扣除的时间
*/
function timeout(diffTime){//主要函数,定时器本体
//....
let runtime=aminTime-diffTime//计算下一次的执行间隔
//....
timer=setTimeout(()=>{
//....
//计算需扣除的时间,并执行下一次的调用
let tmp=startTime
callback(id,runtime,countTimes);
startTime=getTime()
diffTime=(startTime-tmp)-aminTime
timeout(diffTime)
},runtime)
}
//...
}
Запуск и завершение повторяющегося таймера
Запустить повторяющийся таймер просто, а вот остановить — нет. Мы можем завершить текущий повторяющийся таймер, создав новый setTimeout, например, значение выполняется в течение 20 секунд, и оно завершится, если превысит 20 секунд. С этим решением проблем нет, но в приложение добавляется еще один таймер, а еще один таймер означает еще один фактор неопределенности.
Следовательно, мы можем судить о том, истекло ли время ожидания, каждый раз выполняя setTimeout, и если время ожидания истекло, вернуться и не выполнять следующий обратный вызов. Точно так же, если вы хотите контролировать количество выполнений, вы также можете использовать этот метод.
function runTimer(id,aminTime,callback,maxTime,afterTimeUp){
//...
function timeout(diffTime){//主要函数,定时器本体
//....
if(getTime()-usedTime>=maxTime){ //超时清除定时器
cleartimer()
return
}
timer=setTimeout(()=>{
//
if(getTime()-usedTime>=maxTime){ //因为不知道那个时间段会超时,所以都加上判断
cleartimer()
return
}
//..
},runtime)
}
function cleartimer(){//清除定时器
//...
}
function starttimer(){
//...
timeout(0)//因为刚开始执行的时候没有时间差,所以是0
}
return {cleartimer,starttimer}//返回这两个方法,方便调用
}
Остановка по количеству раз, мы можем судить по каждому обратному вызову.
let timer;
timer=runTimer("a",100,function(id,runtime,counts){
if(counts===2){//如果已经执行两次了,则停止继续执行
timer.cleartimer()
}
},1000,function(id,usedTime,counts){})
timer.starttimer()
Благодаря идее остановки таймера в соответствии с указанным выше количеством раз мы можем использовать метод ручной остановки. Создайте параметр, который отслеживает, нужно ли его останавливать, и, если он верен, останавливает таймер.
let timer;
let stop=false
setTimeout(()=>{
stop=true
},200)
timer=runTimer("a",100,function(id,runtime,counts){
if(stop){
timer.cleartimer()
}
},1000,function(id,usedTime,counts){})
timer.starttimer()
setInterval статьи
setInterval эти вещи
Все должны думать, что setTimeout более эффективен, чем setInterval, но факты говорят громче слов, и setInterval немного лучше. Но сделайте setInterval высокопроизводительным повторяющимся таймером, потому что причина, по которой у него так много проблем, бесполезна. Можно сказать, что Interval после авторской трансформации сопоставим с setTimeout.
Инкапсулируйте setInterval в ту же функцию, что и setTimeout выше, включая использование, за исключением того, что setInterval не нужно повторно вызывать себя. Просто контролируйте время в функции обратного вызова.
timer=setInterval(()=>{
if(getTime()-usedTime>=maxTime){
cleartimer()
return
}
countTimes++
callback(id,getTime()-startTime,countTimes);
startTime=getTime();
},aminTime)
Чтобы продемонстрировать производительность Interval, вот волна пиков двух из них.
В нодейс:
В браузере:
Эффективность таймеров при рендеринге или вычислениях не вызывает стресса
В случае большого рендеринга или пересчета давления, эффективности таймера
Во-первых, это производительность каждого в случае без давления, интервал победы!
接下来是很有压力的情况下? .哈哈苍天饶过谁,在相同时间,相同压力的情况下,都出现了跳帧超时,不过两人的原因不一样setTimeout压根没有执行
,а такжеsetInterval是因为抛弃了相同队列下相同定时器的其他callback
То есть сохраняется только первый обратный вызов в очереди, можно сказать, что оба они работают одинаково хорошо.
Другими словами, в случае синхронных операций их производительность не сильно отличается, и можно использовать любой из них. А вот в случае с асинхронностью, типа ajax round robin (websocket не в рамках обсуждения), у нас есть только одна опция setTimeout, тут только одна причина - Бог знает, сколько времени потребуется, чтобы ajax вернулся на этот раз , в данном случае только setTimeout может грамотный.
На самом деле setTimeout не лучше, чем setInterval, за исключением того, что сценарии использования шире, чем setInterval, с точки зрения производительности оба находятся на одном уровне. Так почему? В следующем разделе мы будем диагностировать причину из цикла событий, утечек памяти и сборки мусора.
Цикл событий
Чтобы понять, почему ни один из них не может точно выполнить функцию обратного вызова, давайте начнем с характеристик цикла обработки событий.
JS однопоточный
Прежде чем перейти к делу, давайте сначала обсудим особенности JS. Чем он отличается от других языков программирования? Хотя автор не знаком с другими языками, одно можно сказать наверняка: JS обслуживает браузер, а браузер может напрямую читать JS.
Еще одно часто употребляемое слово для JS — однопоточность. Так что же такое один поток? Буквально, вы можете делать только одно дело за раз. Например, когда вы учитесь, вы не можете заниматься другими делами, вы можете сосредоточиться только на чтении, это отдельный поток. Другой пример: некоторые мамы очень хорошие и могут смотреть телевизор во время вязания свитера, это многопоточность, и они могут делать два дела одновременно.
JS не блокирует
JS — это не только однопоточный, но и неблокирующий язык, что означает, что JS не ждет завершения асинхронной загрузки, такой как чтение интерфейса, и загрузка сетевых ресурсов, таких как изображения и видео. Пропустите асинхронность напрямую и выполните приведенный ниже код. Так разве асинхронная функция никогда не будет выполняться?
eventloop
Поэтому JS, как обрабатывать асинхронный метод обратного вызова? Так появился eventloop, через бесконечный цикл, ищущий квалифицированные функции, выполнение. Но JS занят, если была постоянная задача задачи, JS навсегда не может войти в следующий цикл. JS говорят, что я так устал, я не работаю, и бастуют.
стек и очередь
Так появились стек и очередь, стек — это куча работы JS, которая продолжает выполнять работу, а затем выталкивает задачу из стека. Тогда очередь (queue) — это задачи, которые нужно выполнить в следующем раунде, и все задачи, которые не выполняются, но будут выполняться, попадут в эту очередь. Подождите, пока текущий стек будет очищен и выполнен, затем цикл событий переходит в очередь, а затем помещает задачи в очереди в стек одну за другой.
Это потому, что время цикла событийного цикла зависит от стека. Как и в случае с автобусом, хотя время между остановками можно оценить, несчастные случаи неизбежны, например, пробки, например, слишком много пассажиров, что приводит к слишком долгой посадке, например, случайное попадание на красный свет на каждом перекрестке. обстоятельства и т. д. могут привести к задержке автобуса. Стек событийного цикла является фактором неопределенности.Возможно, задачи в стеке завершаются намного дольше времени, необходимого для помещения задач в очередь, что приводит к отклонениям в каждом времени выполнения.
Диагностика setTimeout и setInterval
Горшок с setInterval еще в те годы - легко вызвать утечку памяти (memory leak)
Когда речь заходит об утечках памяти, нельзя не упомянуть сборку мусора (garbage collection), эти два понятия лучше объясняются вместе, но они — пара хороших друзей. Что такое утечка памяти? Концепция, которая звучит особенно потрясающе, на самом деле заключается в том, что переменные или объекты, которые мы создаем, не перерабатываются системой после того, как они не используются, в результате чего система не выделяет новую память для переменных, которые необходимо создать позже. Проще говоря, это заимствование, а не погашение, и долг велик. Так что алгоритм сборки мусора должен помочь вернуть эту память, но некоторым контентным приложениям они не нужны, а разработчики их не освободили, то есть мне они не нужны, но я их не отпускаю, и сборщик мусора не имеет другого выбора, кроме как пропустить их.Соберите мусор, который был выброшен. Итак, как мы можем сказать алгоритму сборки мусора, мне не нужны эти вещи, можешь их взять? Как можно переработать острую курицу, чтобы освободить место для новой острой курицы? В конце концов, это вопрос привычки программирования.
Существует только одна последняя причина утечки памяти, то есть память, которая не нужна, не освобождается, то есть не освобождаются определенные параметры, так что сборка мусора не может освободить память, что приводит к утечке памяти.
Итак, как распределяется память?
Например, мы определяем константуvar a="apple"
, то в памяти будет выделена строка space village rough apple. Вы можете подумать, что это не просто строка, сколько памяти она может занимать. Да, строки не занимают много памяти, но что, если это массив из тысяч строк? Эта память занимает очень много, если ее вовремя не освободить, последующая работа будет очень сложной.
Но понятие памяти слишком абстрактно, как мы можем почувствовать, сколько памяти это занимает или памяти освобождается? Откройте артефакт «Память» в Chrome, чтобы показать, как ощущается память.
Здесь мы создаем демонстрацию, чтобы проверить, как работает память:
let array=[]//创建数组
createArray()//push内容,增加内存
function createArray(){
for(let j=0;j<100000;j++){
array.push(j*3*5)
}
}
function clearArray(){
array=[]
}
let grow=document.getElementById("grow")
grow.addEventListener("click",clearArray)//点击清除数组内容,也就是清除了内存
Практика — единственный способ обрести истину. С помощью инструмента тестирования Chrome мы можем обнаружить, что очистка содержимого, назначенного переменной, может освободить память, поэтому многие коды заканчиваютсяxxx=null
, то есть освободить память.
Теперь, когда мы знаем, как освобождается память, что насчет памяти, которую нельзя освободить, даже если мы очистим переменную?
Проведен ряд экспериментов, массивами являются переменные, определенные в функции, и глобальные переменные.
let array=[]
createArray()
function createArray(){
for(let j=0;j<100000;j++){
array.push(j*3*5)
}
}
createArray()
function createArray(){
let array=[]
for(let j=0;j<100000;j++){
array.push(j*3*5)
}
}
Результат не удивителен, после запуска функции внутренняя память будет автоматически освобождена без сброса, но глобальная переменная будет существовать всегда. То есть, если переменная поднята, а ссылка не очищена вовремя, память не может быть освобождена.
Есть еще одна ситуация, связанная с dom — создание и удаление dom. Существует классический набор ситуаций, когда свободный DOM не может быть переработан. В следующем коде корень был удален, можно ли переработать дочерние элементы в корне?
let root=document.getElementById("root")
for(let i=0;i<2000;i++){
let div=document.createElement("div")
root.appendChild(div)
}
document.body.removeChild(root)
Ответ - нет, потому что ссылка на корень все еще существует. Хотя она была удалена в DOM, ссылка все еще там. В это время дочерние элементы корня будут существовать в DOM в свободном состоянии и не могут быть переработанный. Решениеroot=null
, очищает ссылку и удаляет дом принудительного состояния.
Если в setInterval есть невосстановимое содержимое, то эта часть памяти никогда не может быть освобождена, что приводит к утечкам памяти. Значит, все-таки дело в привычках программирования, утечках памяти? setInterval не виноват.
Механизм сборки мусора (мусороуборки)
После обсуждения этих причин возникнет утечка памяти, механизм сборки мусора. Существует два основных типа: подсчет ссылок и обмен метками.
подсчет ссылок
Это легче понять, т. е. есть ли ссылка на текущий объект и помечен ли он ссылкой. Последние, которые не отмечены, очищаются. Проблема в том, что два ненужных параметра в программе ссылаются друг на друга, поэтому оба они будут помечены, а потом ни один не может быть удален, то есть заблокирован. Для решения этой проблемы и появился метод замены меток.
mark sweap
Метод развертки пометки (mark swap), этот метод начинается с глобал программы, и помечаются параметры, на которые ссылается глобал. Наконец, очистите все неотмеченные объекты, что может решить проблему, заключающуюся в том, что два объекта ссылаются друг на друга и не могут быть освобождены.
Поскольку он помечен как глобальный, переменные в области действия функции освобождают память после завершения функции.
С помощью механизма сборки мусора мы также можем обнаружить, что содержимое, определенное в global, должно быть осторожным, потому что global эквивалентно функции main, и браузер не очистит эту часть содержимого случайно. Так что помните о проблеме подъема переменной.
Суммировать
Не нашел каменного молотка, чтобы предположить, что setInterval был причиной утечки памяти. Причина утечки памяти — явно плохие привычки кодирования, и setInterval не несет этой вины.