Как сделать, чтобы скрипт выполнялся, но не блокировал рендеринг
Что проверяют
Понимаете ли вы, чем загрузка скрипта отличается от его выполнения и что именно блокирует разбор HTML. Следом почти всегда просят сравнить async и defer и спрашивают, что делать, если скрипт тяжёлый сам по себе.
Как отвечать
Обычный <script src> останавливает парсер: браузер ждёт загрузки и выполнения и только потом строит страницу дальше. Способы это обойти:
defer: файл качается параллельно с разбором, выполняется после того, как документ разобран, но до событияDOMContentLoaded. Порядок нескольких таких скриптов сохраняется, поэтому это выбор по умолчанию для кода приложения;async: тоже качается параллельно, но выполняется сразу по готовности и в любом порядке. Подходит для независимых скриптов вроде аналитики;type="module": модули ведут себя какdeferбез всякого атрибута, а сasyncвыполняются по готовности;- динамическая вставка: скрипт, созданный через
document.createElement('script'), по умолчанию асинхронный. Если важен порядок, ставятscript.async = false; - подсказки загрузки:
<link rel="preload" as="script">илиmodulepreloadначинают скачивание раньше, атрибутfetchpriorityменяет приоритет.
Затем скажите главное: атрибуты решают только проблему загрузки. Пока скрипт выполняется, он занимает основной поток, и страница не отрисовывается и не отвечает на клики. Долгую работу дробят на части, уступая поток через setTimeout или scheduler.yield() там, где он поддерживается, второстепенное откладывают в requestIdleCallback, а тяжёлые вычисления уносят в Web Worker.
Где спотыкаются
Говорят, что async вообще не блокирует. Загрузка действительно идёт в фоне, а вот выполнение прерывает разбор HTML в тот момент, когда файл пришёл.
Ставят async на скрипты, которые зависят друг от друга, например на библиотеку и её плагин. Порядок не гарантирован, и плагин иногда выполняется раньше библиотеки и падает.
Вешают defer на встроенный скрипт без src. Для обычных inline-скриптов атрибут игнорируется, он работает только с внешними файлами.