Недавно я столкнулся с довольно неприятной ошибкой, когда код загружался <select>
динамически через JavaScript. Этот динамически загруженный <select>
имел предварительно выбранное значение. В IE6, мы уже имели код , чтобы установить выбранный <option>
, потому что иногда <select>
«s selectedIndex
значение будет синхронизирован с выбранным <option>
» s index
атрибута, как показано ниже:
field.selectedIndex = element.index;
Однако этот код не работал. Даже при том, что поля selectedIndex
были установлены правильно, неправильный индекс в конечном итоге будет выбран. Однако, если я вставлю alert()
инструкцию в нужное время, будет выбран правильный вариант. Думая, что это может быть какая-то временная проблема, я попробовал что-то случайное, что я видел в коде раньше:
var wrapFn = (function() {
var myField = field;
var myElement = element;
return function() {
myField.selectedIndex = myElement.index;
}
})();
setTimeout(wrapFn, 0);
И это сработало!
У меня есть решение для моей проблемы, но мне неприятно, что я не знаю точно, почему это решает мою проблему. У кого-нибудь есть официальное объяснение? Какую проблему с браузером я избегаю, вызывая свою функцию «позже», используя setTimeout()
?
источник
setTimeout(fn)
так же, какsetTimeout(fn, 0)
, кстати.Ответы:
В вопросе существовало условие гонки между:
Ваш код последовательно выигрывал эту гонку и пытался установить раскрывающийся список до того, как браузер был готов, это означает, что ошибка появится.
Эта гонка существовала, потому что JavaScript имеет единый поток выполнения, который используется для рендеринга страниц. По сути, запуск JavaScript блокирует обновление DOM.
Ваш обходной путь был:
Вызов
setTimeout
с обратным вызовом и нулем в качестве второго аргумента запланирует выполнение обратного вызова асинхронно после кратчайшей возможной задержки - которая будет около 10 мс, когда вкладка находится в фокусе и поток выполнения JavaScript не занят.Поэтому решение ОП заключалось в задержке примерно на 10 мс времени установки выбранного индекса. Это дало браузеру возможность инициализировать DOM, исправляя ошибку.
Каждая версия Internet Explorer демонстрировала причудливое поведение, и временами такой обходной путь был необходим. В качестве альтернативы это могло быть подлинной ошибкой в кодовой базе OP.
Смотрите выступление Филиппа Робертса "Какого черта цикл событий?" для более подробного объяснения.
источник
Предисловие:
Некоторые из других ответов верны, но на самом деле не иллюстрируют, в чем заключается решаемая проблема, поэтому я создал этот ответ, чтобы представить эту детальную иллюстрацию.
Поэтому я публикую подробный обзор того, что делает браузер и как его использование
setTimeout()
помогает . Это выглядит длинновато, но на самом деле очень просто и понятно - я просто сделал это очень подробно.ОБНОВЛЕНИЕ: я сделал JSFiddle, чтобы продемонстрировать объяснение ниже: http://jsfiddle.net/C2YBE/31/ . Большое спасибо @ThangChung за помощь в его запуске.
ОБНОВЛЕНИЕ 2: На случай, если веб-сайт JSFiddle умрет или удалит код, я добавил код к этому ответу в самом конце.
ДЕТАЛИ :
Представьте себе веб-приложение с кнопкой «сделать что-то» и div результата.
onClick
Обработчик для кнопки «сделать что - то» вызывает функцию «LongCalc ()», который делает 2 вещи:Делает очень долгий расчет (скажем, занимает 3 минуты)
Печатает результаты расчета в результате div.
Теперь ваши пользователи начинают тестировать это, нажимают кнопку «сделать что-то», и страница сидит там, ничего не делая, казалось бы, в течение 3 минут, они начинают беспокоиться, нажмите кнопку еще раз, подождите 1 минуту, ничего не происходит, нажмите кнопку еще раз ...
Проблема очевидна - вам нужен DIV «Status», который показывает, что происходит. Посмотрим, как это работает.
Таким образом, вы добавляете DIV «Status» (изначально пустой) и модифицируете
onclick
обработчик (функциюLongCalc()
) для выполнения 4 вещей:Заполните статус «Расчет ... может занять ~ 3 минуты» в статус DIV
Делает очень долгий расчет (скажем, занимает 3 минуты)
Печатает результаты расчета в результате div.
Заполните статус «Расчет выполнен» в статус DIV
И вы с радостью даете приложение пользователям для повторного тестирования.
Они возвращаются к тебе, выглядя очень злыми. И объясните, что когда они нажимали кнопку, статус DIV никогда не обновлялся со статусом «Расчет ...» !!!
Вы почесываете голову, спрашиваете о StackOverflow (или читаете документы, или Google), и понимаете, что проблема:
Браузер помещает все свои задачи «TODO» (как задачи пользовательского интерфейса, так и команды JavaScript), возникающие в результате событий, в одну очередь . И, к сожалению, перерисовка DIV «Status» с новым значением «Calculation ...» - это отдельный TODO, который идет в конец очереди!
Вот разбивка событий во время теста вашего пользователя, содержимое очереди после каждого события:
[Empty]
[Execute OnClick handler(lines 1-4)]
[Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value]
. Обратите внимание, что, хотя изменения DOM происходят мгновенно, для перерисовки соответствующего элемента DOM необходимо новое событие, вызванное изменением DOM, которое произошло в конце очереди .[Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value]
.[Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result]
.[Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value]
.return
изonclick
обработчика sub. Мы убираем обработчик «Execute OnClick» из очереди и начинаем выполнение следующего элемента в очереди.Таким образом, основная проблема заключается в том, что событие перерисовки для DIV «Статус» помещается в очередь в конце, ПОСЛЕ события «Выполнение строки 2», которое занимает 3 минуты, поэтому фактическое перерисовывание не происходит, пока ПОСЛЕ расчета сделано.
На помощь приходит
setTimeout()
. Как это помогает? Потому что, вызывая долго выполняющийся код черезsetTimeout
, вы фактически создаете 2 события:setTimeout
само выполнение и (из-за 0 тайм-аута) отдельную запись в очереди для исполняемого кода.Итак, чтобы решить вашу проблему, вы модифицируете свой
onClick
обработчик так, чтобы он был ДВУМ оператором (в новой функции или просто в блоке внутриonClick
):Заполните статус «Расчет ... может занять ~ 3 минуты» в статус DIV
Выполнить
setTimeout()
с таймаутом 0 и вызовомLongCalc()
функции .LongCalc()
функция почти такая же, как в прошлый раз, но, очевидно, не имеет статуса DIV «Расчет ...» в качестве первого шага; и вместо этого сразу начинает расчет.Итак, как теперь выглядит последовательность событий и очередь?
[Empty]
[Execute OnClick handler(status update, setTimeout() call)]
[Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value]
.[re-draw Status DIV with "Calculating" value]
. В очереди нет ничего нового еще 0 секунд.[re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)]
.[execute LongCalc (lines 1-3)]
. Обратите внимание, что это событие перерисовки может произойти, прежде чем сработает сигнализация, которая работает так же хорошо.Ура! Статус DIV только что обновился до «Расчет ...», прежде чем начался расчет !!!
Ниже приведен пример кода из JSFiddle, иллюстрирующий эти примеры: http://jsfiddle.net/C2YBE/31/ :
HTML код:
Код JavaScript: (выполняется
onDomReady
и может потребовать jQuery 1.9)источник
Взгляните на статью Джона Резига о том, как работают таймеры JavaScript . Когда вы устанавливаете тайм-аут, он фактически ставит в очередь асинхронный код, пока механизм не выполнит текущий стек вызовов.
источник
setTimeout()
покупает вас некоторое время, пока элементы DOM не загрузятся, даже если для него установлено значение 0.Проверьте это: setTimeout
источник
В большинстве браузеров есть процесс, называемый основным потоком, который отвечает за выполнение некоторых задач JavaScript, обновлений пользовательского интерфейса, например: рисование, перерисовка или перекомпоновка и т. Д.
Некоторые задачи исполнения JavaScript и обновления пользовательского интерфейса ставятся в очередь сообщений браузера, а затем отправляются в основной поток браузера для выполнения.
Когда обновления пользовательского интерфейса генерируются, когда основной поток занят, задачи добавляются в очередь сообщений.
setTimeout(fn, 0);
добавьте этоfn
в конец очереди, которая будет выполнена. Он планирует задачу, которая будет добавлена в очередь сообщений через определенный промежуток времени.источник
add this fn to the end of the queue
. Наиболее важным является то, где именноsetTimeout
добавляется эта функция, конец этого цикла цикла или самое начало следующего цикла цикла.Здесь есть противоречивые ответы с отрицательным голосом, и без доказательств невозможно узнать, кому верить. Вот доказательство того, что @DVK прав, а @SalvadorDali неверен. Последний утверждает:
Минимальное время ожидания 4 мсек не имеет отношения к происходящему. Что действительно происходит, так это то, что setTimeout помещает функцию обратного вызова в конец очереди выполнения. Если после setTimeout (callback, 0) у вас есть код блокировки, выполнение которого занимает несколько секунд, обратный вызов не будет выполняться в течение нескольких секунд, пока код блокировки не завершится. Попробуйте этот код:
Выход:
источник
Одна из причин сделать это - отложить выполнение кода до отдельного последующего цикла событий. При ответе на какое-либо событие браузера (например, щелчок мышью) иногда необходимо выполнять операции только после обработки текущего события.
setTimeout()
Объект является самым простым способом сделать это.измените сейчас, что это 2015, я должен отметить, что есть и
requestAnimationFrame()
, что не совсем то же самое, но это достаточно близко кsetTimeout(fn, 0)
тому, что стоит упомянуть.источник
Это старые вопросы со старыми ответами. Я хотел добавить новый взгляд на эту проблему и ответить, почему это происходит, а не почему это полезно.
Итак, у вас есть две функции:
и затем вызовите их в следующем порядке,
f1(); f2();
чтобы увидеть, что второй выполняется первым.И вот почему: невозможно
setTimeout
с задержкой в 0 миллисекунд. Значение Минимальная определяется браузером и не 0 миллисекунд. Исторически браузеры устанавливают этот минимум на 10 миллисекунд, но спецификации HTML5 и современные браузеры устанавливают его на 4 миллисекунды.Также из Mozilla:
PS информация взята после прочтения следующей статьи .
источник
setTimeout(fn,0)
это полезно.setTimeout
/setInterval
более пяти уровней; если нет, то минимум 0 мс (что при отсутствии машин времени). Документы Mozilla расширяют это, чтобы охватить повторяющиеся, а не только вложенные случаи (таким образом,setInterval
с интервалом 0 будет перепланирование сразу несколько раз, а затем задержка дольше), но простому использованиюsetTimeout
с минимальным вложением разрешается немедленно ставить в очередь.Так как ему передается длительность
0
, я предполагаю, что это для того, чтобы удалить код, переданный вsetTimeout
поток выполнения. Так что если это функция, которая может занять некоторое время, она не помешает выполнению последующего кода.источник
Оба из этих двух самых популярных ответов неверны. Посмотрите описание MDN для модели параллелизма и цикла обработки событий , и вам должно стать ясно, что происходит (этот ресурс MDN - настоящая жемчужина). И простое использование
setTimeout
может добавить неожиданные проблемы в ваш код в дополнение к «решению» этой маленькой проблемы.Здесь на самом деле происходит не то, что «браузер может быть еще не совсем готов, потому что параллелизм» или что-то на основе «каждая строка - это событие, которое добавляется в конец очереди».
Jsfiddle обеспечивается ДВК действительно иллюстрирует проблему, но его объяснения этому не является правильным.
Что происходит в его коде, так это то, что он сначала присоединяет обработчик события к
click
событию на#do
кнопке.Затем, когда вы действительно нажимаете кнопку,
message
создается ссылка, ссылающаяся на функцию-обработчик события, которая добавляется вmessage queue
. Когда оноevent loop
достигает этого сообщения, оно создаетframe
в стеке вызов функции для обработчика события click в jsfiddle.И вот тут это становится интересным. Мы настолько привыкли считать Javascript асинхронным, что склонны упускать из виду этот крошечный факт: любой кадр должен быть выполнен полностью, прежде чем следующий кадр может быть выполнен . Нет параллелизма, люди.
Что это значит? Это означает, что всякий раз, когда функция вызывается из очереди сообщений, она блокирует очередь до тех пор, пока сгенерированный ею стек не будет очищен. Или, в более общих чертах, он блокируется, пока функция не вернется. И он блокирует все , включая операции рендеринга DOM, прокрутки и еще много чего. Если вам нужно подтверждение, просто попробуйте увеличить продолжительность длительной операции в скрипте (например, запустить внешний цикл еще 10 раз), и вы заметите, что во время его работы вы не можете прокручивать страницу. Если он работает достаточно долго, ваш браузер спросит вас, хотите ли вы завершить процесс, потому что он делает страницу не отвечающей. Кадр выполняется, и цикл событий и очередь сообщений застряли до его завершения.
Так почему же этот побочный эффект текста не обновляется? Потому что в то время как вы уже изменили значение элемента в DOM - вы можете
console.log()
его значение сразу после его изменения и видеть , что он был изменен (который показывает , почему объяснение ДВК не правильно) - браузер ждет стек истощать (on
функция-обработчик для возврата) и, таким образом, сообщение для завершения, так что оно может в конечном итоге приступить к выполнению сообщения, которое было добавлено средой выполнения в качестве реакции на нашу операцию мутации, и чтобы отразить эту мутацию в пользовательском интерфейсе ,Это потому, что мы на самом деле ждем завершения кода. Мы не сказали «кто-то получит это, а затем вызовет эту функцию с результатами, спасибо, и теперь я закончил с возвращением imma, делайте что угодно сейчас», как мы обычно делаем с нашим основанным на событиях асинхронным JavaScript. Мы вводим функцию-обработчик события щелчка, мы обновляем элемент DOM, вызываем другую функцию, другая функция работает долгое время и затем возвращается, затем мы обновляем тот же элемент DOM, а затем возвращаемся из исходной функции, фактически опустошая стек. И тогда браузер может перейти к следующему сообщению в очереди, которое вполне может быть сообщением, сгенерированным нами путем запуска некоторого внутреннего события типа «on-DOM-mutation».
Пользовательский интерфейс браузера не может (или не хочет) обновлять пользовательский интерфейс до тех пор, пока не завершится текущий выполняемый кадр (функция вернулась). Лично я думаю, что это скорее дизайн, чем ограничение.
Почему
setTimeout
вещь работает тогда? Это происходит потому, что он эффективно удаляет вызов долго выполняющейся функции из своего собственного фрейма, планируя ее последующее выполнение вwindow
контексте, чтобы он сам мог немедленно вернуться и позволить очереди сообщений обрабатывать другие сообщения. И идея заключается в том, что сообщение «при обновлении» пользовательского интерфейса, которое было сгенерировано нами в Javascript при изменении текста в DOM, теперь опережает сообщение, помещенное в очередь для долго выполняющейся функции, поэтому обновление пользовательского интерфейса происходит до того, как мы заблокируем надолго.Обратите внимание, что а) долго выполняющаяся функция все еще блокирует все, когда она выполняется, и б) вы не гарантированы, что обновление пользовательского интерфейса действительно опережает его в очереди сообщений. В моем браузере Chrome в июне 2018 года значение
0
не «решает» проблему, которую демонстрирует скрипка - 10 делает. Я на самом деле немного задушен этим, потому что мне кажется логичным, что сообщение об обновлении пользовательского интерфейса должно быть поставлено в очередь перед ним, так как его триггер выполняется перед планированием долгосрочной функции, которая будет запущена «позже». Но, возможно, в движке V8 есть некоторые оптимизации, которые могут помешать, или, может быть, мне просто не хватает понимания.Итак, в чем проблема с использованием
setTimeout
, и что является лучшим решением для этого конкретного случая?Во-первых, проблема с использованием
setTimeout
любого обработчика событий, подобного этому, чтобы попытаться смягчить другую проблему, склонна связываться с другим кодом. Вот реальный пример из моей работы:Коллега, в неправильном понимании цикла событий, попытался «потянуть» Javascript, используя некоторый код рендеринга шаблона
setTimeout 0
для его рендеринга. Его больше нет здесь, чтобы спрашивать, но я могу предположить, что, возможно, он вставил таймеры для измерения скорости рендеринга (что было бы возвращением непосредственности функций) и обнаружил, что использование этого подхода приведет к невероятно быстрым ответам от этой функции.Первая проблема очевидна; вы не можете использовать javascript, поэтому вы ничего не выиграете, пока добавляете запутывание. Во-вторых, вы теперь эффективно отсоединили рендеринг шаблона от стека возможных прослушивателей событий, которые могут ожидать, что этот шаблон был отрендерен, хотя вполне возможно, что это не так. Реальное поведение этой функции было теперь недетерминированным, как и, по незнанию, любая функция, которая будет запускать ее или зависеть от нее. Вы можете делать обоснованные предположения, но вы не можете правильно кодировать его поведение.
«Исправить» при написании нового обработчика событий , который зависит от его логики было также использовать
setTimeout 0
. Но это не исправление, это трудно понять, и неинтересно отлаживать ошибки, вызванные таким кодом. Иногда проблем не возникает, в других случаях происходит постоянный сбой, и, опять же, иногда он работает и прерывается время от времени, в зависимости от текущей производительности платформы и того, что еще происходит в данный момент. Поэтому лично я бы посоветовал не использовать этот хак (он является хак, и мы все должны знать , что это такое), если вы действительно не знаете , что вы делаете , и каковы его последствия.Но что можем мы делать вместо этого? Что ж, как указано в упомянутой статье MDN, либо разделите работу на несколько сообщений (если можете), чтобы другие сообщения, находящиеся в очереди, могли чередоваться с вашей работой и выполнялись во время ее выполнения, либо используйте веб-работника, который может запустить в тандеме с вашей страницей и возвращать результаты, когда закончите с ее расчетами.
О, и если вы думаете: «Ну, я не могу просто добавить обратный вызов в длительную функцию, чтобы сделать ее асинхронной?», Тогда нет. Обратный вызов не делает его асинхронным, ему все равно придется выполнить долго выполняющийся код, прежде чем явно вызвать ваш обратный вызов.
источник
Другая вещь, которую это делает, это выдвигает вызов функции в конец стека, предотвращая переполнение стека, если вы рекурсивно вызываете функцию. Это имеет эффект
while
цикла, но позволяет механизму JavaScript запускать другие асинхронные таймеры.источник
push the function invocation to the bottom of the stack
. Тоstack
, о чем ты говоришь, неясно. Наиболее важным является то, где именноsetTimeout
добавляется эта функция, конец этого цикла цикла или самое начало следующего цикла цикла.Вызывая setTimeout, вы даете странице время реагировать на то, что делает пользователь. Это особенно полезно для функций, запускаемых во время загрузки страницы.
источник
Некоторые другие случаи, когда setTimeout полезен:
Вы хотите разбить длительный цикл или вычисление на более мелкие компоненты, чтобы браузер не зависал и не говорил «Сценарий на странице занят».
Вы хотите отключить кнопку отправки формы при нажатии, но если отключить кнопку в обработчике onClick, форма не будет отправлена. setTimeout со временем ноль делает трюк, позволяя завершить событие, начать отправку формы, после чего ваша кнопка может быть отключена.
источник
Ответы о циклах выполнения и рендеринге DOM до завершения какого-либо другого кода являются правильными. Нулевое время ожидания в JavaScript помогает сделать код псевдо-многопоточным, хотя это не так.
Я хочу добавить, что значение BEST для кросс-браузерного / кроссплатформенного тайм-аута на ноль секунд в JavaScript на самом деле составляет около 20 миллисекунд вместо 0 (нуля), потому что многие мобильные браузеры не могут регистрировать тайм-ауты менее 20 миллисекунд из-за ограничений времени на чипах AMD.
Кроме того, длительные процессы, которые не связаны с манипулированием DOM, теперь должны быть отправлены веб-работникам, поскольку они обеспечивают истинное многопоточное выполнение JavaScript.
источник
setTimout в 0 также очень полезен в шаблоне настройки отложенного обещания, которое вы хотите вернуть сразу:
источник
Проблема заключалась в том, что вы пытались выполнить операцию Javascript на несуществующем элементе. Элемент еще не был загружен и
setTimeout()
дает больше времени для загрузки элемента следующими способами:setTimeout()
вызывает событие быть асинхронным, поэтому выполняется после всего синхронного кода, давая вашему элементу больше времени для загрузки. Асинхронные обратные вызовы, такие как обратный вызовsetTimeout()
, помещаются в очередь событий и помещаются в стек циклом событий. после того, как стек синхронного кода пуст.setTimeout()
часто немного выше (4-10 мс в зависимости от браузера). Это немного большее время, необходимое для выполненияsetTimeout()
обратных вызовов, вызвано количеством 'тиков' (когда тик помещает обратный вызов в стек, если стек пуст) цикла обработки событий. Из-за производительности и срока службы батареи количество тактов в цикле событий ограничено определенной величиной менее 1000 раз в секунду.источник
Javascript является однопоточным приложением, поэтому не позволяет одновременно запускать функции, поэтому для достижения этого используются циклы событий. Так что именно то, что setTimeout (fn, 0) делает это, помещается в квест задачи, который выполняется, когда ваш стек вызовов пуст. Я знаю, что это объяснение довольно скучно, поэтому я рекомендую вам просмотреть это видео, оно поможет вам, как все работает в браузере. Проверьте это видео: - https://www.youtube.com/watch?time_continue=392&v=8aGhZQkoFbQ
источник