Тег <script> подключает к странице JavaScript - код, который меняет разметку, реагирует на действия посетителя и обращается к серверу без перезагрузки страницы. Код можно прописать прямо между тегами <script> и </script>, а можно вынести в отдельный файл и подключить через атрибут src - тем же принципом, каким <link> подключает внешний файл CSS. <script> можно прописать и в <head>, и в body, и не один раз на странице - но от места и атрибутов зависит, когда именно браузер его выполнит.
В этой статье
<script src="app.js"></script>
<script>
console.log('код прямо в HTML');
</script>
<script type="module" src="app.mjs"></script>
Код между тегами или файл через src
У <script>, в отличие от <link>, есть два равноправных способа задать код - через атрибут src или прямо между открывающим и закрывающим тегом. Но если указать оба способа сразу, браузер не выполнит оба - он скачает и выполнит только внешний файл из src, а всё, что написано между тегами, просто проигнорирует.
<script src="app.js">
console.log('этот код браузер не выполнит');
</script>
Пример: что выполнится на самом деле
Включите или выключите src и нажмите на кнопку - ниже настоящий результат выполнения тега с текущими настройками:
Это настоящее выполнение тега <script> браузером, а не имитация: при включённом src реально скачивается и выполняется отдельный файл (в примере - через data-адрес вместо обычного пути), а код между тегами реально остаётся без внимания.
async и defer: когда выполняется скрипт
Без дополнительных атрибутов обычный <script src="..."> останавливает разбор HTML на своём месте: браузер сначала полностью скачивает и выполняет файл и только потом читает страницу дальше. Для страницы с несколькими скриптами это заметно замедляет отображение, поэтому у <script> есть два атрибута, которые убирают эту остановку - но по-разному.
<script src="app.js"></script>
<script src="app.js" async></script>
<script src="app.js" defer></script>
Оба атрибута требуют src и не работают на коде между тегами. Разница между ними - в том, что происходит, как только файл скачался: async выполняет его сразу же, прерывая разбор HTML на короткий момент, а порядок нескольких async-скриптов относительно друг друга не гарантирован. defer ничего не прерывает - откладывает выполнение до конца разбора всего HTML и выполняет скрипты строго по порядку, в котором они стоят на странице. Отсюда и практическое правило: если скрипту важен порядок или доступ к уже построенной разметке - нужен defer, а если скрипт независимый от остальных, вроде счётчика или виджета, - подойдёт async.
Пример: три схемы загрузки
Выберите атрибут - ниже условная временная шкала (не точный хронометраж в секундах, а соотношение событий):
Остальные атрибуты: type module, integrity, nonce
| Атрибут | Для чего |
|---|---|
type="module" | Подключает ES-модуль - код с import/export. Модули всегда откладываются как с defer, даже без этого атрибута явно |
blocking="render" | Просит задержать первую отрисовку страницы до этого скрипта. Нужен только скриптам, добавленным через JS - те, что браузер находит при обычном разборе head, и так блокируют отрисовку по умолчанию |
integrity | Хэш файла для проверки, что подключаемый по src скрипт не подменили на сервере или CDN. По спецификации не используется без src |
nonce | Одноразовый код, который разрешает выполнение именно этого скрипта при включённой политике безопасности CSP (Content-Security-Policy) |
crossorigin | Разрешает браузеру показать подробности ошибки во внешнем скрипте с другого домена, а не общее «Script error» |
Как делать не надо при работе с тегом script
С <script> ошибки почти всегда связаны не с назначением тега, а с порядком его выполнения или с синтаксисом вокруг src:
| Ошибка | Что происходит | Как исправить |
|---|---|---|
Указаны и src, и код между тегами | Браузер выполняет только внешний файл из src, код между тегами полностью игнорируется (см. пример выше) | Выбрать что-то одно - либо src, либо код между тегами |
Скрипт с обращением к элементам страницы находится в head без async/defer | Элементы, к которым обращается код (например через getElementById), ещё не существуют в момент выполнения - скрипт падает с ошибкой или просто не находит их | Использовать defer, либо переносить обычный <script> в конец body, после нужной разметки |
javascript: в href ссылки вместо обработчика события | Ссылка формально работает, но ломает доступность - не работает открытие в новой вкладке, не виден статус ссылки, хуже работает со скринридерами | Обычный href на реальный адрес, а клик обрабатывать через addEventListener или атрибут onclick |
Самозакрывающийся <script src="..." /> | В HTML (в отличие от XML) самозакрытие для script не поддерживается - браузер продолжает ждать отдельный закрывающий тег и может утащить в «тело» скрипта часть страницы дальше | Всегда закрывать явным </script>, даже при пустом содержимом и наличии src |
integrity указан без src | Атрибут проверяет файл, который скачивается по адресу из src - без него проверять нечего, указание бессмысленно | Использовать integrity только вместе с src на внешний файл |