Как подключить JavaScript к HTML и когда выполняется код

HTML / DOM и события

Как подключить JavaScript к HTML и когда выполняется код

Подключение JavaScript-модулей, defer и async, работа с DOM, события и безопасная организация клиентского кода.

Опубликовано 04.09.2026 5 минут чтения
Содержание статьи

HTML создаёт структуру страницы, CSS оформляет её, а JavaScript добавляет поведение. Браузер разбирает разметку и строит DOM — объектное представление документа. Скрипт находит элементы DOM, подписывается на события и меняет состояние интерфейса.

Для проектного кода предпочтителен внешний ES-модуль:

HTML
<script type="module" src="/assets/js/main.js"></script>​

Модуль выполняется после разбора HTML, работает в строгом режиме и поддерживает import/export.

Первый интерактивный пример

HTML
<button type="button" data-counter>Нажатий: <span>0</span></button><script type="module" src="/assets/js/counter.js"></script>​

JavaScript
const button = document.querySelector('[data-counter]');​if (button) {  const value = button.querySelector('span');  let count = 0;​  button.addEventListener('click', () => {    count += 1;    value.textContent = String(count);  });}​

Кнопка остаётся настоящей кнопкой, поэтому работает с мышью, клавиатурой и вспомогательными технологиями. JavaScript отвечает только за изменение счётчика.

Почему внешний файл удобнее

Скрипт внутри HTML допустим для короткой демонстрации, но в проекте внешний модуль:

  • не смешивает разметку и поведение;
  • переиспользуется на нескольких страницах;
  • кешируется браузером;
  • разбивается на небольшие модули;
  • лучше сочетается со строгой Content Security Policy.

Не используйте inline-обработчики:

HTML
<!-- Не рекомендуется --><button onclick="openMenu()">Меню</button>​

Подписка через addEventListener() позволяет нескольким модулям реагировать на событие и не создаёт глобальные функции.

Обычный script, defer и async

Скрипт без атрибутов

HTML
<script src="/assets/js/legacy.js"></script>​

Когда парсер встречает такой тег, загрузка и выполнение могут остановить дальнейший разбор документа. Если скрипт находится в <head> и обращается к ещё не созданным элементам, querySelector() вернёт null.

defer

HTML
<script defer src="/assets/js/legacy.js"></script>​

Файл загружается параллельно и выполняется после разбора HTML. Несколько deferred-скриптов сохраняют порядок подключения. Атрибут предназначен для классических внешних скриптов.

async

HTML
<script async src="https://example.com/analytics.js"></script>​

Скрипт выполняется сразу после загрузки, порядок относительно других файлов не гарантирован. Это подходит для независимого кода, который не ждёт DOM и не зависит от соседних скриптов, например некоторых аналитических интеграций.

type="module"

Модули по умолчанию ведут себя как deferred-скрипты. Для них не нужно добавлять defer.

HTML
<script type="module" src="/assets/js/main.js"></script>​

У модулей собственная область видимости. Переменная верхнего уровня не становится свойством window.

Разделение кода на модули

JavaScript
// format-price.jsexport function formatPrice(value) {  return new Intl.NumberFormat('ru-RU', {    style: 'currency',    currency: 'RUB',  }).format(value);}​

JavaScript
// main.jsimport { formatPrice } from './format-price.js';​const price = document.querySelector('[data-price]');​if (price) {  const value = Number(price.dataset.price);  if (Number.isFinite(value)) {    price.textContent = formatPrice(value);  }}​

В браузере локальные импорты требуют точного относительного пути и обычно расширения .js. Открывайте проект через HTTP-сервер, а не двойным кликом по файлу: политика модулей и запросов для file:// отличается.

Нужно ли ждать DOMContentLoaded

Классический скрипт в <head> без defer может выполниться слишком рано. ES-модуль и deferred-скрипт запускаются после построения документа, поэтому дополнительный обработчик обычно не нужен.

Если код загружается другим способом и момент выполнения неизвестен, можно проверить состояние документа:

JavaScript
function init() {  // Поиск элементов и подписка на события.}​if (document.readyState === 'loading') {  document.addEventListener('DOMContentLoaded', init, { once: true });} else {  init();}​

События и поведение по умолчанию

Событие передаёт объект с информацией об источнике. Не отменяйте поведение браузера без причины.

JavaScript
const form = document.querySelector('[data-search-form]');​form?.addEventListener('submit', (event) => {  const query = new FormData(form).get('q');​  if (typeof query !== 'string' || query.trim() === '') {    event.preventDefault();    form.querySelector('[name="q"]')?.focus();  }});​

Если JavaScript не загрузится, обычная HTML-форма всё равно должна выполнить базовый сценарий. Такой подход называется progressive enhancement.

Асинхронные операции

Сетевой запрос не блокирует весь интерфейс. await приостанавливает только текущую async-функцию:

JavaScript
async function loadProfile() {  const response = await fetch('/api/profile', {    headers: { Accept: 'application/json' },  });​  if (!response.ok) {    throw new Error(`HTTP ${response.status}`);  }​  return response.json();}​try {  const profile = await loadProfile();  console.log(profile);} catch (error) {  console.error('Не удалось загрузить профиль', error);}​

Даже при клиентской проверке сервер остаётся источником истины для прав, валидации и сохранения данных.

Частые ошибки

  • скрипт запускается до появления нужного элемента;
  • селектор может вернуть null, но код сразу обращается к свойству;
  • обработчики записаны в onclick и зависят от глобальных функций;
  • async используют для файлов, которые зависят друг от друга;
  • пользовательский текст вставляют через innerHTML вместо textContent;
  • интерфейс полностью перестаёт работать без JavaScript;
  • ошибки fetch() не обрабатываются и пользователь не получает обратную связь.

Что запомнить

Подключайте проектный JavaScript как внешний ES-модуль, работайте с нативными элементами и событиями через addEventListener(). Учитывайте момент выполнения, проверяйте результат поиска DOM-элементов, безопасно вставляйте текст и сохраняйте базовый сценарий работоспособным без скрипта.

Сообщить об ошибке в статье

Рекомендации редакции

Другие публикации из этого раздела.

Новые материалы

Получайте практические разборы без информационного шума

Одна общая рассылка о новых статьях и инструментах. Адрес будет активироваться только после подтверждения.

Подписка появится после подключения сервиса рассылок и Yandex SmartCaptcha.