JavaScript подключается к HTML через тег <script>. Скрипт может быть встроенным (inline) или внешним (external). Место размещения и атрибуты async/defer определяют порядок загрузки и выполнения скрипта.
Ключевые аспекты
Inline-скрипт — код непосредственно между <script>...</script>
External-скрипт — подключение файла через src="script.js"
defer — скрипт загружается параллельно, выполняется после парсинга HTML
async — скрипт загружается параллельно, выполняется сразу после загрузки
Размещение — в <head> или перед </body>
Порядок выполнения
Атрибут
Загрузка
Выполнение
Порядок
(нет)
Блокирует
Сразу
По порядку
defer
Параллельно
После DOM
По порядку
async
Параллельно
После загрузки
Не гарантирован
Рекомендации
Используйте defer для большинства скриптов
async — для независимых скриптов (аналитика, виджеты)
Размещайте в <head> с defer или перед </body> без атрибутов
Частые ошибки на собеседованиях
Путают async и defer — async не гарантирует порядок выполнения
Не знают, что без атрибутов скрипт блокирует парсинг HTML
Думают, что defer работает с inline-скриптами (не работает)
Забывают, что type="module" по умолчанию работает как defer
Введение и проблематика
JavaScript — язык программирования для добавления интерактивности на веб-страницы. Но как браузер узнаёт, какой код выполнять? Через тег <script>, который связывает HTML-документ с JavaScript-кодом.
От правильного подключения скриптов зависит:
Производительность — скорость загрузки страницы
Корректность — доступ к DOM-элементам
Порядок выполнения — зависимости между скриптами
Неправильное подключение скриптов — одна из самых частых причин ошибки "Cannot read property of null" у начинающих разработчиков.
Базовая теория
Способы подключения скриптов
html
<!-- Код прямо в HTML --><script>console.log('Hello, World!');functiongreet(name) {alert('Привет, '+ name); }</script>
Когда использовать:
Очень маленькие скрипты
Критичный код для первой отрисовки
Одноразовый код для конкретной страницы
Атрибуты тега script
Атрибут
Описание
src
Путь к внешнему файлу
defer
Отложенное выполнение
async
Асинхронная загрузка
type
Тип скрипта (module, text/javascript)
nomodule
Fallback для браузеров без ES-модулей
Порядок загрузки и выполнения
Без атрибутов (классический способ)
html
<scriptsrc="script.js"></script>
Браузер встречает тег script
Парсинг HTML останавливается.
Загрузка скрипта
Браузер загружает файл (блокирующе).
Выполнение скрипта
Код выполняется немедленно.
Продолжение парсинга
HTML-парсинг возобновляется.
text
Парсинг HTML ──▶ СТОП ──▶ Загрузка JS ──▶ Выполнение ──▶ Парсинг HTML
⚠️
Классический способ блокирует рендеринг страницы. Пользователь видит белый экран, пока скрипт загружается и выполняется.
С атрибутом defer
html
<scriptsrc="script.js"defer></script>
Загрузка происходит параллельно с парсингом HTML
Выполнение после полного парсинга HTML
Порядок выполнения скриптов сохраняется
text
Парсинг HTML ────────────────────────────▶ DOM Ready ──▶ Выполнение JS └──▶ Загрузка JS (параллельно) ──▶
С атрибутом async
html
<scriptsrc="script.js"async></script>
Загрузка происходит параллельно с парсингом HTML
Выполнение сразу после загрузки (прерывает парсинг)
Порядок выполнения НЕ гарантирован
text
Парсинг HTML ──▶ СТОП ──▶ Выполнение JS ──▶ Парсинг HTML └──▶ Загрузка JS ──▶
Практические примеры
Сравнение поведения
html
<!DOCTYPEhtml><html><head> <title>Script Loading</title><!-- 1. Без атрибутов: блокирует --> <scriptsrc="blocking.js"></script><!-- 2. defer: выполнится после парсинга HTML --> <scriptsrc="deferred.js"defer></script><!-- 3. async: выполнится когда загрузится --> <scriptsrc="async.js"async></script></head><body> <h1>Страница</h1><!-- 4. Перед </body>: DOM уже готов --> <scriptsrc="bottom.js"></script></body></html>
Типичная структура проекта
html
<!DOCTYPEhtml><html><head> <metacharset="UTF-8"> <title>My App</title><!-- Критичные стили --> <linkrel="stylesheet"href="styles.css"><!-- Основной скрипт с defer --> <scriptsrc="app.js"defer></script><!-- Независимые скрипты с async --> <scriptsrc="analytics.js"async></script></head><body> <divid="root"></div></body></html>
Доступ к DOM-элементам
html
<head> <script>// ❌ Ошибка: элемент ещё не существуетconstbtn=document.getElementById('myBtn');btn.addEventListener('click', handler); // Cannot read property 'addEventListener' of null </script></head><body> <buttonid="myBtn">Click me</button></body>
// math.jsexportfunctionsum(a, b) {return a + b;}// app.jsimport { sum } from'./math.js';console.log(sum(2,3)); // 5
Fallback для старых браузеров
html
<!-- Для современных браузеров --><scripttype="module"src="app.modern.js"></script><!-- Для старых браузеров (IE11) --><scriptnomodulesrc="app.legacy.js"></script>
Пограничные кейсы
defer и inline-скрипты
🚫
defer НЕ работает с inline-скриптами!
html
<!-- defer игнорируется --><scriptdefer>console.log('Выполнится сразу, не отложенно');</script><!-- Так работает --><scriptsrc="script.js"defer></script>
Порядок async-скриптов
html
<!-- Порядок выполнения НЕ гарантирован! --><scriptsrc="jquery.js"async></script><scriptsrc="app.js"async></script> <!-- может выполниться раньше jQuery -->
Динамическая загрузка
javascript
// Программное создание скриптаconstscript=document.createElement('script');script.src ='plugin.js';script.async =false; // важно для сохранения порядкаdocument.head.appendChild(script);
Сравнительная таблица
Способ
Блокирует
Порядок
DOM готов
Когда использовать
<script>
✅ Да
✅ Да
❌ Нет
Критичный код
defer
❌ Нет
✅ Да
✅ Да
Основные скрипты
async
⚠️ Частично
❌ Нет
❌ Нет
Независимые скрипты
type="module"
❌ Нет
✅ Да
✅ Да
ES6+ проекты
Перед </body>
❌ Нет
✅ Да
✅ Да
Legacy-подход
Вопросы интервьюера
Q: Чем defer отличается от async?
defer — скрипт выполняется после парсинга HTML, порядок сохраняется. async — выполняется сразу после загрузки, порядок не гарантирован.
Q: Где лучше размещать скрипты?
В <head> с атрибутом defer. Или перед </body> без атрибутов для legacy-браузеров.
Q: Почему скрипт не видит DOM-элемент?
Скрипт выполняется до парсинга HTML с этим элементом. Используйте defer, DOMContentLoaded или разместите скрипт после элемента.
Q: Работает ли defer с inline-скриптами?
Нет, defer работает только с внешними скриптами (src="...").
Q: Что такое type="module"?
ES6-модуль с поддержкой import/export. По умолчанию ведёт себя как defer и имеет свой scope.