Скрипт можно запустить в консоли DevTools, но для реальной страницы JavaScript подключается через тег <script>. Важно понимать, где он стоит в HTML и как браузер выполняет код - от этого зависит, найдутся ли элементы на странице и в каком порядке сработают файлы.
Базовый вариант - script без атрибутов
Простейшее подключение внешнего файла:
<script src="/js/app.js"></script>
Что происходит по умолчанию:
- Браузер читает HTML сверху вниз.
- Когда встречается
<script src="...">, загрузка HTML останавливается. - Браузер скачивает файл скрипта.
- Скрипт выполняется.
- Только потом браузер продолжает разбор HTML.
Это называется “blocking” (блокирующее подключение).
Почему так часто появляются ошибки с DOM
Если скрипт стоит в <head> и пытается найти элементы, которых еще нет в HTML, можно получить null:
<head>
<script src="/js/app.js"></script>
</head>
<body>
<button id="btn">OK</button>
</body>
// app.js
const btn = document.querySelector("#btn");
console.log(btn); // null
Причина простая - #btn еще не был разобран браузером, когда скрипт выполнился.
Где ставить script
Есть два распространенных места: в <head> или в конце <body>. Оба работают, но с разными правилами.
В конце body
Классический подход - поставить script перед закрывающим </body>:
<body>
<button id="btn">OK</button>
<script src="/js/app.js"></script>
</body>
Плюсы:
- почти всегда DOM уже построен
- не нужен
defer
Минусы:
- когда проектов много, трудно поддерживать порядок подключений
- скрипты “размазаны” по разметке (если их много)
В head с defer
Современный удобный подход - держать подключения в <head>, но использовать defer:
<head>
<script src="/js/app.js" defer></script>
</head>
defer - главный атрибут для большинства страниц
defer меняет поведение загрузки и выполнения:
- файл скачивается параллельно с разбором HTML
- выполнение откладывается до момента, когда HTML уже разобран
- несколько скриптов с
deferвыполняются в том порядке, в котором идут в документе
Пример с несколькими файлами:
<head>
<script src="/js/lib.js" defer></script>
<script src="/js/app.js" defer></script>
</head>
Порядок будет таким:
- HTML разбирается
- Скрипты скачиваются параллельно
- Выполнится
lib.js - Выполнится
app.js
Это важно, если app.js зависит от кода из lib.js.
async - когда порядок не важен
async тоже позволяет скачивать файл параллельно, но есть ключевые отличия:
- скрипт выполнится сразу после загрузки, не дожидаясь конца разбора HTML
- порядок между несколькими
asyncне гарантируется
<head>
<script src="/js/metrics.js" async></script>
</head>
Типичное применение - аналитика, счетчики, виджеты, где не важен порядок и нет зависимости от DOM в момент выполнения.
Inline script - код прямо в HTML
Иногда скрипт пишут прямо внутри тега <script>:
<script>
console.log("inline script");
</script>
Правила те же: если такой блок стоит до элементов DOM, код выполнится раньше появления элементов.
Часто inline используют для небольших настроек, например передачи переменной в JS:
<script>
window.APP_VERSION = "1.0.0";
</script>
<script src="/js/app.js" defer></script>
Здесь важно учитывать порядок: app.js должен видеть window.APP_VERSION.
Важный момент - DOMContentLoaded
Событие DOMContentLoaded срабатывает, когда HTML разобран и DOM построен (картинки и стили могут еще догружаться).
Если script подключен без defer и стоит в <head>, можно подождать DOM так:
document.addEventListener("DOMContentLoaded", () => {
const btn = document.querySelector("#btn");
console.log(btn);
});
Но в большинстве случаев проще и чище подключать файл с defer.
Порядок выполнения - короткая шпаргалка
<script src="..." >без атрибутов:- блокирует разбор HTML
- выполняется сразу при встрече
<script src="..." defer>:- не блокирует разбор HTML
- выполняется после разбора HTML
- сохраняет порядок между defer-скриптами
<script src="..." async>:- не блокирует разбор HTML
- выполняется сразу после загрузки
- порядок между async-скриптами не гарантируется
Частые проблемы и быстрые проверки
Скрипт не запускается
- Проверить путь в
src(опечатка, неверная папка). - Открыть DevTools -> Network и убедиться, что файл загружается без 404.
- Проверить Console на ошибки.
Код видит null вместо элементов
- Скрипт выполняется слишком рано.
- Решения: перенести в конец
<body>, добавитьdefer, или обернуть код вDOMContentLoaded.
Не работает зависимость между файлами
- Скрипты должны выполняться в правильном порядке.
- Для нескольких файлов чаще всего нужен
deferи корректный порядок в HTML.
Короткий итог
Для обычных учебных и прикладных страниц самый удобный и безопасный вариант - подключать скрипты в <head> с defer. Это дает корректный порядок выполнения, не блокирует разбор HTML и позволяет коду работать с DOM без дополнительных обходных конструкций.