Angular Front-end Инженер

Angular Front-end Инженер

Роадмап навыков для прокачки

Добавление скриптов

Browser APIHTMLОсновное

Основная идея

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!');
 
  function greet(name) {
    alert('Привет, ' + name);
  }
</script>

Когда использовать:

  • Очень маленькие скрипты
  • Критичный код для первой отрисовки
  • Одноразовый код для конкретной страницы

Атрибуты тега script

АтрибутОписание
srcПуть к внешнему файлу
deferОтложенное выполнение
asyncАсинхронная загрузка
typeТип скрипта (module, text/javascript)
nomoduleFallback для браузеров без ES-модулей

Порядок загрузки и выполнения

Без атрибутов (классический способ)

html
<script src="script.js"></script>

Браузер встречает тег script

Парсинг HTML останавливается.

Загрузка скрипта

Браузер загружает файл (блокирующе).

Выполнение скрипта

Код выполняется немедленно.

Продолжение парсинга

HTML-парсинг возобновляется.

text
Парсинг HTML ──▶ СТОП ──▶ Загрузка JS ──▶ Выполнение ──▶ Парсинг HTML
⚠️

Классический способ блокирует рендеринг страницы. Пользователь видит белый экран, пока скрипт загружается и выполняется.

С атрибутом defer

html
<script src="script.js" defer></script>
  • Загрузка происходит параллельно с парсингом HTML
  • Выполнение после полного парсинга HTML
  • Порядок выполнения скриптов сохраняется
text
Парсинг HTML ────────────────────────────▶ DOM Ready ──▶ Выполнение JS
     └──▶ Загрузка JS (параллельно) ──▶

С атрибутом async

html
<script src="script.js" async></script>
  • Загрузка происходит параллельно с парсингом HTML
  • Выполнение сразу после загрузки (прерывает парсинг)
  • Порядок выполнения НЕ гарантирован
text
Парсинг HTML ──▶ СТОП ──▶ Выполнение JS ──▶ Парсинг HTML
     └──▶ Загрузка JS ──▶

Практические примеры

Сравнение поведения

html
<!DOCTYPE html>
<html>
<head>
  <title>Script Loading</title>
 
  <!-- 1. Без атрибутов: блокирует -->
  <script src="blocking.js"></script>
 
  <!-- 2. defer: выполнится после парсинга HTML -->
  <script src="deferred.js" defer></script>
 
  <!-- 3. async: выполнится когда загрузится -->
  <script src="async.js" async></script>
</head>
<body>
  <h1>Страница</h1>
 
  <!-- 4. Перед </body>: DOM уже готов -->
  <script src="bottom.js"></script>
</body>
</html>

Типичная структура проекта

html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>My App</title>
 
  <!-- Критичные стили -->
  <link rel="stylesheet" href="styles.css">
 
  <!-- Основной скрипт с defer -->
  <script src="app.js" defer></script>
 
  <!-- Независимые скрипты с async -->
  <script src="analytics.js" async></script>
</head>
<body>
  <div id="root"></div>
</body>
</html>

Доступ к DOM-элементам

html
<head>
  <script>
    // ❌ Ошибка: элемент ещё не существует
    const btn = document.getElementById('myBtn');
    btn.addEventListener('click', handler); // Cannot read property 'addEventListener' of null
  </script>
</head>
<body>
  <button id="myBtn">Click me</button>
</body>

Решения:

html
<head>
  <script src="app.js" defer></script>
</head>
<body>
  <button id="myBtn">Click me</button>
</body>
 
<!-- app.js -->
<script>
const btn = document.getElementById('myBtn'); // ✅ Работает
btn.addEventListener('click', handler);
</script>

ES-модули

type="module"

html
<script type="module" src="app.js"></script>

Особенности модулей:

  • По умолчанию ведут себя как defer
  • Поддерживают import/export
  • Строгий режим ('use strict') по умолчанию
  • Свой scope (переменные не попадают в window)
javascript
// math.js
export function sum(a, b) {
  return a + b;
}
 
// app.js
import { sum } from './math.js';
console.log(sum(2, 3)); // 5

Fallback для старых браузеров

html
<!-- Для современных браузеров -->
<script type="module" src="app.modern.js"></script>
 
<!-- Для старых браузеров (IE11) -->
<script nomodule src="app.legacy.js"></script>

Пограничные кейсы

defer и inline-скрипты

🚫

defer НЕ работает с inline-скриптами!

html
<!-- defer игнорируется -->
<script defer>
  console.log('Выполнится сразу, не отложенно');
</script>
 
<!-- Так работает -->
<script src="script.js" defer></script>

Порядок async-скриптов

html
<!-- Порядок выполнения НЕ гарантирован! -->
<script src="jquery.js" async></script>
<script src="app.js" async></script> <!-- может выполниться раньше jQuery -->

Динамическая загрузка

javascript
// Программное создание скрипта
const script = 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.


Источники