Правильное подключение ресурсов JavaScript на страницу

Раздел: Веб-разработка -> Подключение ресурсов

Методы интеграции скриптов

Подключение JavaScript к веб странице требует выбора подходящего метода в зависимости от целей: производительность, порядок выполнения, поддержка старых браузеров. Рассмотрим наиболее распространенные способы.

Рекомендуемый подход: атрибуты async и defer

Для внешних скриптов, которые не изменяют DOM напрямую, эффективно использовать атрибут async. Если скрипт зависит от DOM, предпочтительнее defer. Оба атрибута позволяют браузеру продолжать парсинг HTML во время загрузки скрипта, избегая блокировки рендеринга.

<script src='script.js' async></script>
<script src='deferred.js' defer></script>

Asset js (подключение asset-файлов в javascript)

Пример: первая загрузка произойдет сразу после получения файла, вторая дождется полного разбора документа.

Типичные проблемы:

Для async порядок выполнения не гарантируется. Если скрипты зависят друг от друга, следует использовать defer или комбинировать.

Defer скрипты выполняются перед событием DOMContentLoaded, но после того, как все синхронные скрипты отработают. Если в defer скрипте используется document.write, это приведет к ошибке.

Как выполнить скрипт сразу после загрузки без ожидания DOM?

Использование атрибута async. Скрипт загружается асинхронно и выполняется сразу после получения, не дожидаясь парсинга документа.

<script src='analytics.js' async></script>

Resource js (ресурсы javascript (resource))

Подходит для независимых счетчиков, рекламы.

Если скрипт требует DOM, может возникнуть ошибка обращения к несуществующему элементу. Решение: проверять существование элементов перед использованием.

Как загрузить скрипт после полного разбора HTML?

Атрибут defer гарантирует, что скрипт выполнится после того, как весь HTML будет обработан, но до события DOMContentLoaded.

<script src='app.js' defer></script>

Идеально для скриптов, которым нужен доступ к DOM, например, инициализация интерфейса.

Если страница содержит несколько defer скриптов, они выполняются в порядке объявления. Однако, если один из них загружается дольше, он может заблокировать выполнение последующих. Решение: минимизировать количество defer скриптов или использовать модули.

Как динамически добавить скрипт на страницу?

Создается элемент script через document.createElement, задаются атрибуты src, async (по умолчанию true), затем добавляется в DOM.

const script = document.createElement('script');
script.src = 'sdk.js';
script.async = false; // если нужен порядок
script.onload = () => console.log('Скрипт загружен');
script.onerror = () => console.error('Ошибка загрузки');
document.body.appendChild(script);

Полезно для загрузки виджетов, аналитики под условием.

Если не установить async=false, скрипт будет асинхронным, что может нарушить порядок. Также отсутствие обработчика onerror приведет к молчаливой ошибке. Решение: всегда добавлять обработчики событий.

Как использовать модули ES6?

Указание type='module' включает поддержку импорта и экспорта. Модули загружаются с отложенным выполнением (аналог defer) и имеют изолированную область видимости.

<script type='module' src='main.mjs'></script>
<!-- внутри main.mjs: import { helper } from './helper.mjs'; -->

Современный способ организации кода.

Модули не работают при открытии файла локально (file://) из-за политики CORS. Для тестирования необходим веб сервер. Также старые браузеры (IE11) не поддерживают модули. Решение: использовать сборщики (Webpack, Rollup) для создания единого бандла.

Как отложить загрузку скрипта до взаимодействия пользователя?

Использовать Intersection Observer или слушать событие click/scroll для динамической загрузки скрипта. Пример: ленивая загрузка чата.

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const script = document.createElement('script');
      script.src = 'chat.js';
      document.body.appendChild(script);
      observer.unobserve(entry.target);
    }
  });
});
observer.observe(document.querySelector('#chat-placeholder'));

Экономит трафик и ускоряет начальную загрузку.

Если пользователь быстро скроллит, скрипт может не успеть загрузиться к моменту взаимодействия. Решение: предзагрузка (preload) или предварительное получение с помощью .

Как подключить скрипт из CDN и обеспечить fallback?

Указывается основной URL из CDN, а затем проверяется загрузка; в случае ошибки подгружается локальная копия.

<script src='https://cdn.example.com/lib.js'></script>
<script>
if (typeof MyLib === 'undefined') {
  var fallback = document.createElement('script');
  fallback.src = 'lib/local.js';
  document.head.appendChild(fallback);
}
</script>

Гарантирует работоспособность при недоступности CDN.

Если основной скрипт загружается, но не определяет глобальную переменную, fallback не сработает. Решение: проверять конкретное свойство, которое точно должно быть.

Как показать сообщение, если JavaScript отключен?

Тег noscript содержит контент, который отображается только при выключенном JavaScript.

<noscript>
  <p>Для работы сайта необходим JavaScript.</p>
</noscript>

Обеспечивает минимальную информативность.

Если JavaScript включен, но скрипты блокируются (например, расширениями), noscript не сработает. Решение: не полагаться только на noscript, использовать graceful degradation.

Расширенные примеры работы с ресурсами JavaScript

Пример 1: Сравнение async, defer и обычного скрипта. Покажем порядок выполнения с помощью console.log.

Пример
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <script>console.log('inline head')</script>
  <script src='async.js' async></script>
  <script src='defer.js' defer></script>
  <script src='sync.js'></script>
</head>
<body>
  <script>console.log('inline body')</script>
</body>
</html>

// async.js
console.log('async script');

// defer.js
console.log('defer script');

// sync.js
console.log('sync script');
Порядок вывода (один из возможных):
inline head
sync script
inline body
async script
defer script

Пояснение: синхронный скрипт блокирует парсинг, выполняется сразу. async может выполниться перед или после inline body, defer всегда после полного разбора HTML.

Пример 2: Динамическая загрузка с Promise и обработкой ошибок.

Пример
function loadScript(src) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = src;
    script.onload = resolve;
    script.onerror = () => reject(new Error(`Failed to load ${src}`));
    document.head.appendChild(script);
  });
}

loadScript('lib.js')
  .then(() => console.log('lib.js loaded'))
  .catch(err => console.error(err));
При успешной загрузке: 'lib.js loaded'
При ошибке: 'Error: Failed to load lib.js'

Пример 3: Использование type='module' с импортом и экспортом.

Пример
<!-- index.html -->
<script type='module'>
  import { greet } from './utils.mjs';
  greet('Мир');
</script>

// utils.mjs
export function greet(name) {
  console.log(`Привет, ${name}!`);
}
Вывод: 'Привет, Мир!'

Пример 4: Ленивая загрузка скрипта при нажатии кнопки.

Пример
<button id='loadWidget'>Загрузить виджет</button>
<script>
document.getElementById('loadWidget').addEventListener('click', () => {
  const script = document.createElement('script');
  script.src = 'widget.js';
  script.onload = () => widget.init();
  document.body.appendChild(script);
});
</script>
После клика загружается widget.js и вызывается widget.init().

Пример 5: Fallback для CDN с несколькими источниками.

Пример
<script src='https://cdn1.example.com/jquery.js'></script>
<script>
window.jQuery || document.write('<script src=\'https://cdn2.example.com/jquery.js\'><\/script>');
</script>
Если первый CDN недоступен, загрузится второй. document.write используется здесь осторожно, но может быть заменен на createElement.

Пример 6: Предзагрузка критических скриптов с помощью link rel='preload'.

Пример
<link rel='preload' href='critical.js' as='script'>
<script src='critical.js' defer></script>
Браузер начинает загрузку critical.js раньше, чем парсер доходит до тега script, что ускоряет выполнение.

Пример 7: Использование nomodule для старых браузеров.

Пример
<script type='module' src='modern.js'></script>
<script nomodule src='legacy.js'></script>
Современные браузеры загрузят modern.js, старые (без поддержки модулей) загрузят legacy.js.

Ресурсы JavaScript (resource) - comments

En
Resource js (javascript)