Скрипт на странице всегда выполняется «внутри» какого-то окна: вкладки, попапа или iframe. Это окно в JavaScript представлено объектом window, и у него две роли одновременно. Во-первых, это глобальный объект: именно в нём оказываются глобальные переменные и функции вроде parseInt() или setTimeout(). Во-вторых, через него доступна BOM (Browser Object Model, объектная модель браузера) — набор объектов для всего, что находится за пределами самой страницы: адресной строки, истории переходов, экрана, самого браузера.
Официального стандарта под названием «BOM» нет: это собирательное имя. Но почти всё, о чём пойдёт речь, давно описано в спецификации HTML и одинаково работает во всех браузерах. Разберём по порядку: как устроена глобальная область, что лежит в location, history, navigator и screen, как открывать окна и показывать диалоги — и какими API эти вещи сегодня лучше заменять. События окна (load, resize, scroll и другие) здесь не повторяем: для них есть отдельный гид по событиям объекта window.
window как глобальный объект
У каждой среды выполнения JavaScript есть свой глобальный объект. В браузере это window, в Node.js — global, в веб-воркере — self. Встроенные функции — его методы, поэтому две строки ниже делают одно и то же:
window.setTimeout(() => console.log('Прошла секунда'), 1000);
setTimeout(() => console.log('Прошла секунда'), 1000);
Обычно префикс window. опускают: писать короче, и код легче переносится в другую среду, где такого объекта нет.
Какие переменные попадают в window, а какие нет
Часто говорят, что «все глобальные переменные — это свойства window». На деле это правда только наполовину и зависит от того, как переменная объявлена:
var visits = 1;
let total = 2;
const LIMIT = 3;
function greet() {}
console.log(window.visits); // 1
console.log(window.greet); // ƒ greet() {}
console.log(window.total); // undefined
console.log(window.LIMIT); // undefined
console.log(total); // 2 — переменная глобальная, просто не свойство window
В window оказываются только var и объявления функций. Глобальные let, const и class видны во всех обычных скриптах страницы, но хранятся отдельно и свойствами окна не становятся. А в скриптах с type="module" на верхнем уровне объявляется переменная модуля, а не глобальная.
Отдельная история — присваивание без объявления вообще:
function setScore() {
score = 10; // опечатка или забытый let
}
setScore();
console.log(window.score); // 10 — утекло в глобальную область
В нестрогом режиме такая строка молча создаёт свойство на window. В строгом режиме (а также внутри модулей и классов, где он включён автоматически) она бросает ReferenceError, и ошибка сразу видна. Почему это одна из главных причин существования 'use strict' — в разборе строгого режима.
Как проверить, что глобальная переменная существует
Обращение к необъявленному имени бросает ошибку, поэтому такая проверка сама роняет скрипт:
if (analytics) { // ReferenceError: analytics is not defined
analytics.track('open');
}
Раньше вместо этого писали if (window.analytics): чтение несуществующего свойства не бросает ошибку, а возвращает undefined. Приём рабочий, но с двумя дырами: он не видит глобальные let/const и считает «несуществующей» переменную со значением 0, '' или false. Надёжнее оператор typeof: для необъявленного имени он возвращает строку 'undefined' без исключения.
if (typeof analytics !== 'undefined') {
analytics.track('open');
}
// если нужно именно свойство окна (например, глобал от сторонней библиотеки)
if ('dataLayer' in window) {
window.dataLayer.push({ event: 'open' });
}
globalThis: одно имя для любой среды
Если код должен работать и в браузере, и в Node.js, и в воркере, привязываться к window нельзя. Старый трюк — взять this на верхнем уровне скрипта:
const root = this; // window — но только в обычном скрипте
Этот способ ломается в модулях: там this на верхнем уровне равен undefined. Внутри функций в строгом режиме тоже. Для этой задачи в язык добавили globalThis — ссылку на глобальный объект, которая одинаково называется в любой среде:
globalThis === window; // true в браузере
globalThis === global; // true в Node.js
globalThis === self; // true в веб-воркере
globalThis.appConfig = { theme: 'dark' }; // доступно отовсюду
location: адрес текущей страницы
Свойство window.location хранит объект с URL (адресом) страницы, разложенным на части. Удобнее всего увидеть их на одном адресе, где есть всё сразу:
// открыта страница https://shop.example.com:8080/catalog/shoes?color=red&size=42#reviews
console.log(location.pathname); // '/catalog/shoes'
| Свойство | Значение | Что это |
| href | весь адрес целиком | полный URL строкой |
| protocol | 'https:' | протокол, с двоеточием на конце |
| host | 'shop.example.com:8080' | домен вместе с портом |
| hostname | 'shop.example.com' | только домен |
| port | '8080' | порт; пустая строка, если он стандартный и не указан |
| pathname | '/catalog/shoes' | путь после домена |
| search | '?color=red&size=42' | строка запроса с вопросительным знаком; пустая, если параметров нет |
| hash | '#reviews' | якорь с решёткой; пустой, если его нет |
| origin | 'https://shop.example.com:8080' | протокол + домен + порт; только для чтения |
Почти все свойства, кроме origin, можно не только читать, но и записывать. Браузер сразу применит изменение: присвоение href или pathname уведёт на новую страницу, а смена hash лишь прокрутит к якорю, без перезагрузки.
location.hash = '#reviews'; // прокрутка к блоку с id="reviews"
location.href = 'https://example.com/'; // переход на другой сайт
Методы location
- assign(url) — переходит по адресу, как присвоение href. Текущая страница остаётся в истории, кнопка «Назад» вернёт на неё.
- replace(url) — тоже переходит, но подменяет текущую запись в истории. Вернуться кнопкой «Назад» не получится. Это то, что нужно после логина или для редиректа со старого адреса.
- reload() — перезагружает страницу. В старых статьях встречается reload(true) «в обход кэша», но этот параметр нестандартный: его понимал только Firefox, остальные браузеры аргумент игнорируют.
- toString() — возвращает тот же полный адрес, что и href.
// после успешного входа: со страницы логина на кабинет, без возврата назад
location.replace('/account');
Параметры запроса удобнее разбирать через URL
Свойство location.search отдаёт параметры одной сырой строкой — '?color=red&size=42'. Резать её вручную через split('&') неудобно и чревато ошибками с кодированием символов. Для этого есть объекты URL и URLSearchParams:
const url = new URL(location.href);
url.searchParams.get('color'); // 'red'
url.searchParams.get('size'); // '42' — всегда строка
url.searchParams.has('sort'); // false
// меняем параметр и обновляем адресную строку без перезагрузки
url.searchParams.set('size', '43');
history.replaceState(null, '', url);
URL разбирает на те же части любой адрес, а не только текущий, — например, ссылку из пользовательского ввода. Попробовать можно в демо ниже.
history: переходы вперёд и назад
Объект window.history даёт управлять историей переходов текущей вкладки — той самой, по которой ходят кнопки «Назад» и «Вперёд». Сами адреса посещённых страниц из него не прочитать: это было бы утечкой данных о пользователе.
history.back(); // как кнопка «Назад»
history.forward(); // как кнопка «Вперёд»
history.go(-2); // на две страницы назад
history.go(1); // на одну вперёд
history.go(0); // перезагрузить текущую
Свойство history.length возвращает число записей в истории вкладки включая текущую, так что на только что открытой вкладке оно равно 1, а не 0.
Методы pushState() и replaceState() меняют адрес в строке браузера без загрузки новой страницы. На них годами держалась маршрутизация в SPA (одностраничных приложениях): приложение само рисует нужный экран, а pushState() добавляет запись в историю, чтобы работала кнопка «Назад».
function openTab(name) {
renderTab(name);
history.pushState({ tab: name }, '', `?tab=${name}`);
}
// пользователь нажал «Назад» или «Вперёд»
window.addEventListener('popstate', (event) => {
renderTab(event.state?.tab ?? 'overview');
});
Navigation API: современная замена history
У связки pushState() + popstate есть известные неудобства: событие не срабатывает на клики по обычным ссылкам и на сам pushState(), а историю целиком не прочитать. Поэтому в браузерах появился объект window.navigation. Он сообщает о любом переходе в пределах документа одним событием navigate и позволяет перехватить его, чтобы отрисовать страницу самостоятельно:
navigation.addEventListener('navigate', (event) => {
// переходы на другие сайты, загрузки файлов и т.п. перехватить нельзя
if (!event.canIntercept) return;
const url = new URL(event.destination.url);
if (!url.pathname.startsWith('/docs/')) return;
event.intercept({
async handler() {
const html = await loadDocsPage(url.pathname);
renderPage(html);
},
});
});
Здесь одним обработчиком обслуживаются и клики по ссылкам, и отправка форм, и кнопки «Назад»/«Вперёд» — никаких ручных pushState(). Заодно есть navigation.entries() со списком записей истории этого сайта, navigation.currentEntry и промис-версии navigation.back()/navigation.navigate(url). API недавно доехал до всех основных движков, так что в коде, который должен работать и на старых версиях браузеров, пока оставляем проверку и запасной путь через history:
if ('navigation' in window) {
// маршрутизация через navigate
} else {
// pushState + popstate
}
navigator и screen: что известно о браузере и экране
navigator.userAgent и почему по нему не стоит определять браузер
Объект window.navigator описывает сам браузер. Самое известное его свойство — userAgent, строка, которой браузер представляется сайтам:
navigator.userAgent;
// 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36'
Строка выглядит так, будто браузер одновременно Mozilla, Safari и Chrome, — это наследие десятилетий, когда каждый новый браузер притворялся популярным, чтобы сайты не отдавали ему урезанную версию. Пользователь может подменить её в настройках или расширением. А Chrome к тому же урезал строку ради приватности: младшие цифры версии в ней заменены нулями, версия Windows заморожена на 10.0 даже в Windows 11.
Структурированная замена — navigator.userAgentData (User-Agent Client Hints): бренд, мобильность и платформа уже разобраны по полям. Но это экспериментальный API, который есть не во всех движках, поэтому без проверки его не трогаем:
if (navigator.userAgentData) {
console.log(navigator.userAgentData.mobile); // false
console.log(navigator.userAgentData.platform); // 'Windows'
}
В большинстве задач браузер определять вообще не нужно. Правильный вопрос не «это Safari?», а «умеет ли браузер вот это?». Отвечает на него проверка возможностей (feature detection):
// есть системное меню «Поделиться» — показываем кнопку
if ('share' in navigator) {
shareButton.hidden = false;
}
// основное устройство ввода — палец, а не мышь
if (matchMedia('(pointer: coarse)').matches) {
document.body.classList.add('touch');
}
Из остального в navigator в реальных проектах чаще всего нужны language (предпочитаемый язык интерфейса, например 'uk-UA'), onLine (есть ли сеть) и clipboard для работы с буфером обмена.
screen: размеры монитора, а не окна
Объект window.screen описывает экран, на котором открыт браузер:
screen.width; // 1920 — ширина экрана в CSS-пикселях
screen.height; // 1080
screen.availWidth; // 1920 — без панелей операционной системы
screen.availHeight; // 1032 — например, минус панель задач
screen.colorDepth; // 24 — глубина цвета в битах
Здесь легко промахнуться: размер экрана почти никогда не нужен для вёрстки. Окно браузера может занимать половину монитора, а страница живёт во вьюпорте — видимой области окна. Её размеры лежат в самом window:
window.innerWidth; // ширина вьюпорта вместе с полосой прокрутки
document.documentElement.clientWidth; // ширина вьюпорта без полосы прокрутки
window.devicePixelRatio; // 2 на Retina — сколько физических пикселей в одном CSS-пикселе
Разницу между screen.width и innerWidth удобно пощупать в демо: меняйте ширину окна и смотрите, какие числа двигаются, а какие нет.
А решения вида «экран меньше 768 пикселей — прячем меню» вообще лучше отдавать CSS-медиазапросам или matchMedia() в JS, а не читать screen: медиазапрос сам реагирует на изменение размера окна.
Ориентация экрана
На мобильных устройствах полезен screen.orientation: текущая ориентация и событие её смены.
console.log(screen.orientation.type); // 'portrait-primary' или 'landscape-primary'
console.log(screen.orientation.angle); // 0, 90, 180 или 270
screen.orientation.addEventListener('change', () => {
player.classList.toggle('wide', screen.orientation.type.startsWith('landscape'));
});
Метод screen.orientation.lock('landscape') фиксирует ориентацию, например для игры или видео. Но работает он обычно только в полноэкранном режиме (как его включить — в статье про Fullscreen API), и поддерживается заметно уже, чем само свойство. Поэтому вызов всегда оборачиваем в try/catch:
async function playFullscreen() {
await video.requestFullscreen();
try {
await screen.orientation.lock('landscape');
} catch {
// не получилось — просто играем в текущей ориентации
}
}
Окна и попапы
Метод window.open() открывает новое окно или вкладку и возвращает ссылку на объект window этого окна:
const helpWindow = window.open('/help.html', 'help', 'popup,width=480,height=600');
Аргументы часто путают, поэтому по порядку:
- Адрес — что открыть.
- Имя окна (target), а вовсе не его заголовок. Заголовок берётся из <title> открытой страницы. Имя работает как у атрибута target ссылки: '_blank' — всегда новое окно, а повторный вызов с тем же именем ('help') загрузит адрес в уже открытое окно, не плодя новые.
- Строка параметров через запятую без пробелов. Ключевое слово popup просит отдельное маленькое окно вместо вкладки, width/height задают размер, left/top — положение. Если строку не передать, браузер откроет обычную вкладку.
Закрыть окно можно методом close() через сохранённую ссылку. Скрипту разрешено закрывать только те окна, которые открыл он сам, — вкладку, которую открыл пользователь, window.close() не закроет.
helpWindow.close();
console.log(helpWindow.closed); // true
Как два окна после этого обмениваются данными (postMessage, opener, BroadcastChannel), разобрано в статье о том, как передавать данные между окнами и вкладками.
Блокировщик попапов и null вместо окна
Попапы были главным инструментом навязчивой рекламы, поэтому все браузеры их ограничивают. window.open() обычно срабатывает только как прямая реакция на действие пользователя — клик или нажатие клавиши. Вызов из таймера, после долгого await или при загрузке страницы блокировщик остановит. В этом случае метод вернёт null, и это стоит обработать:
helpButton.addEventListener('click', () => {
const win = window.open('/help.html', 'help', 'popup,width=480,height=600');
if (!win) {
// попап заблокирован — показываем справку прямо на странице
helpPanel.hidden = false;
}
});
Одна ловушка: с параметром noopener (он отрезает новому окну доступ к window.opener и нужен при открытии чужих сайтов) open() возвращает null всегда, даже если окно успешно открылось. Так что проверка на null имеет смысл только без этого параметра.
У окна есть ещё методы moveTo(x, y) и resizeTo(width, height), которые двигают окно и меняют его размер. Сегодня они работают только для окон, открытых через window.open(), и не работают, если в окне больше одной вкладки. Двигать и растягивать окно пользователя — плохая идея с точки зрения удобства, так что почти всегда есть решение лучше.
Диалоги alert, confirm, prompt и их замена
Три метода окна показывают системные диалоговые окна. Они не входят в стандарт ECMAScript (сам язык), это часть браузерного окружения:
alert('Файл сохранён'); // undefined — просто сообщение с кнопкой OK
const ok = confirm('Удалить черновик?'); // true — OK, false — Отмена
const name = prompt('Как вас зовут?', 'Мария'); // введённая строка или null при Отмене
Второй аргумент prompt() — значение, которое заранее подставится в поле ввода. Если пользователь нажмёт OK, ничего не введя, вернётся пустая строка, а не null, — эти два случая стоит различать.
Главная особенность всех трёх — они блокирующие. Пока диалог открыт, JavaScript на странице стоит на этой строке: не срабатывают таймеры, не обрабатываются ответы сервера, не перерисовывается интерфейс. Иногда это даже удобно: confirm() гарантирует, что удаление не начнётся, пока пользователь думает. Но оформить такие окна нельзя, выглядят они в каждом браузере по-своему, а после нескольких подряд браузер предлагает пользователю запретить их для сайта — и дальше они просто перестают появляться. Полагаться на них в рабочем интерфейсе нельзя.
Современная замена — HTML-элемент <dialog>. Его можно стилизовать, он не останавливает скрипты, а модальный режим, фокус и закрытие по Esc браузер берёт на себя:
<dialog id="delete-dialog">
<form method="dialog">
<p>Удалить черновик?</p>
<button value="cancel">Отмена</button>
<button value="delete">Удалить</button>
</form>
</dialog>
const dialog = document.querySelector('#delete-dialog');
deleteButton.addEventListener('click', () => dialog.showModal());
dialog.addEventListener('close', () => {
// returnValue — value нажатой кнопки внутри form method="dialog"
if (dialog.returnValue === 'delete') {
deleteDraft();
}
});
Разница в подходе: confirm() возвращает ответ сразу, а у <dialog> ответ приходит позже, в событии close. Код становится асинхронным, зато страница продолжает жить.
document.write() — наследие, которое лучше не трогать
Через window.document доступна сама страница — DOM. Почти всё в нём давно стандартно и полезно, но один метод стоит упомянуть как антипример. document.write() вставляет строку прямо в поток HTML, пока браузер разбирает страницу:
<h1><script>document.write('Привет, мир!')</script></h1>
Пока страница грузится, текст окажется внутри заголовка. Но если вызвать тот же метод после загрузки (например, по клику), он сотрёт весь документ и оставит на странице только переданную строку. Вдобавок каждый вызов заставляет браузер останавливать разбор HTML, а Chrome на медленных соединениях и вовсе блокирует сторонние скрипты, вставленные через document.write(). MDN помечает метод как нерекомендуемый. Вместо него — обычные DOM-методы:
const title = document.querySelector('h1');
title.textContent = 'Привет, мир!'; // просто текст
title.insertAdjacentHTML('afterend', '<p>Подзаголовок</p>'); // кусок разметки после элемента
Шпаргалка
| Объект / метод | Для чего | Что взять вместо или в дополнение |
| window как глобал | глобальные переменные, встроенные функции | globalThis для кода под любую среду |
| location | части адреса, переходы, перезагрузка | URL и URLSearchParams для разбора |
| history | назад/вперёд, pushState() в SPA | navigation и событие navigate |
| navigator.userAgent | строка-представление браузера | проверка возможностей, matchMedia() |
| screen | размер монитора, ориентация | innerWidth и медиазапросы для вёрстки |
| open() / close() | попапы и новые вкладки | проверка на null, запасной вариант на странице |
| alert() / confirm() / prompt() | быстрые системные диалоги | <dialog> |
| document.write() | вставка HTML при разборе страницы | textContent, insertAdjacentHTML(), append() |
Если коротко: window — это одновременно глобальная область и дверь во всё, что окружает страницу. Большая часть этих объектов старше многих фронтендеров, и у многих уже есть более удобные и безопасные преемники. Старые API никуда не денутся и в чужом коде встретятся ещё не раз, но в новом коде стоит тянуться к преемникам.
Комментарии (0)