JavaScript в браузере и подключение скриптов
Этот урок открывает фронтенд-серию. Он не про React, не про сборщики
и не про CSS-фреймворки. Он про то, где живёт JS в браузере, какие у
него глобалы, как подключаются скрипты и почему <script src=...> без
атрибутов в 2026 году пахнет легаси.
Если ты уже год пишешь на React, скорее всего ты можешь объяснить
useEffect, но не объяснишь, чем defer отличается от async. Этот
зазор лечим сразу: всё, что выше React, опирается на эти знания.
1. Один глобал на одну вкладку
Открыл вкладку, получил один window.
Открыл вторую, получил второй. Они не делят память напрямую: разные
вкладки общаются через BroadcastChannel, localStorage-события,
сервис-воркер или серверный API. Изнутри JS-кода window это глобальный
объект: всё, что объявлено без let/const/var в верхнем скоупе,
попадает в его владение.
console.log(window === globalThis); // true в браузере
console.log(window.innerWidth); // 1440 или сколько у тебя
// "обнажённое" присваивание создаёт свойство window
foo = 42;
console.log(window.foo); // 42
В Node globalThis тоже есть, но это не window. Это global, с
другим набором свойств: нет document, нет location.
Есть некоторые общие API (setTimeout, Promise, URL, fetch), но не все.
2. BOM: окно и его соседи
Браузерная объектная модель (Browser Object Model) это набор объектов, которые описывают саму вкладку. Не контент страницы (это DOM, тема урока 02), а вокруг.
| Объект | Что внутри |
|---|---|
window | глобал, размеры окна, таймеры, фокус |
document | формально DOM, но висит на window.document |
location | URL текущей страницы, href, pathname, search, hash |
history | стек переходов, pushState, replaceState, back, forward |
navigator | браузер и платформа, userAgent, language, clipboard, serviceWorker |
screen | физический экран, width, height, pixelDepth |
Это всё, что нужно знать про BOM для начала. Большая часть твоего кода
не будет напрямую трогать window.screen, но location.pathname и
history.pushState лежат в основе любого SPA-роутера.
// Навигация без перезагрузки страницы. Это основа React Router.
history.pushState({ from: 'chat' }, '', '/channels/general');
// URL поменялся, но страница не перезагрузилась.
// Слушаем кнопку "Назад".
window.addEventListener('popstate', (event) => {
console.log('back to', location.pathname, event.state);
});
3. Как подключать скрипты
Тег <script> существует с 1995 года и за это время оброс правилами.
Сегодня правильно одно из трёх:
<!-- 1. Классический script с defer -->
<script defer src="/app.js"></script>
<!-- 2. ES-модуль (defer по умолчанию) -->
<script type="module" src="/app.js"></script>
<!-- 3. importmap, чтобы маппить bare imports без сборщика -->
<script type="importmap">
{
"imports": {
"lodash": "https://cdn.skypack.dev/lodash"
}
}
</script>
<script type="module">
import { chunk } from 'lodash';
console.log(chunk([1, 2, 3, 4], 2));
</script>
Что делает каждое поведение:
<script src=...>без атрибутов. Блокирует HTML-парсер. Браузер останавливается, качает файл, выполняет, продолжает парсинг. Единственный кейс, где это допустимо, это критичный inline-скрипт безsrc. Пример: чтение темы изlocalStorageдо первой отрисовки (first paint), чтобы не было вспышки белого.<script defer src=...>. Скачивается параллельно с парсингом, выполняется послеDOMContentLoaded, сохраняя порядок междуdefer-скриптами. Это самый предсказуемый вариант для классического приложения.<script async src=...>. Скачивается параллельно, выполняется как только скачался, без гарантии порядка. Подходит только для независимых скриптов (аналитика, рекламный пиксель), которым плевать на остальной код.<script type="module">. ES-модули в браузере. Подразумеваетdefer, поддерживаетimport, выполняется в строгом режиме. Это дефолт для новых приложений.<script type="importmap">. Не код, а словарь. Позволяет писатьimport x from 'lodash'без сборщика: браузер сам зарезолвит по таблице.
Слова про порядок и блокировку проще увидеть, чем прочитать. Пощёлкай
один и тот же набор из двух скриптов под тремя стратегиями: где парсер
встаёт, в каком порядке выполняется код и куда попадает
DOMContentLoaded.
Правило: новый проект пишем на type="module". Inline-скрипт без
src только для критичной инициализации тёмной темы. defer оставляем
для legacy-страниц, где type="module" нельзя из-за поддержки
старых браузеров.
дальше после входа
Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.
Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.