Краткое руководство по глобальной области видимости в JavaScript
Когда вы объявляете переменную на верхнем уровне JavaScript-файла, где она фактически находится? Ответ зависит от того, пишете ли вы классический скрипт или ES-модуль — и ошибка в этом вопросе приводит к неочевидным багам, которые сложно отлаживать.
Это руководство объясняет, как работает глобальная область видимости в JavaScript при современной разработке, почему globalThis является надёжным способом обращения к глобальному объекту, и как var, let и const ведут себя по-разному на верхнем уровне.
Ключевые выводы
- Поведение глобальной области видимости различается между классическими скриптами и ES-модулями — только
varв классических скриптах создаёт свойства глобального объекта. letиconstсоздают глобальные привязки, но не присоединяются к глобальному объекту.- Используйте
globalThisдля кроссплатформенной совместимости, когда вам нужен доступ к глобальному объекту. - Предпочитайте ES-модули для автоматической изоляции области видимости и предотвращения непреднамеренного загрязнения пространства имён.
Что такое глобальная область видимости в JavaScript?
Глобальная область видимости (global scope) — это самая внешняя область видимости в вашей JavaScript-среде. Переменные в глобальной области видимости доступны из любого места вашего кода — внутри функций, блоков или вложенных модулей.
Но вот критическое различие, которое упускают большинство руководств: область видимости верхнего уровня в JavaScript ведёт себя по-разному в зависимости от того, как загружается ваш скрипт.
Классические скрипты vs ES-модули
В классическом скрипте (загруженном без type="module"), переменные верхнего уровня, объявленные с помощью var, становятся свойствами глобального объекта:
// classic script
var config = { debug: true }
console.log(globalThis.config) // { debug: true }
С ES-модулями это полностью меняется. Модули и глобальная область видимости работают по-разному по дизайну. Привязки верхнего уровня в модуле остаются в пределах области видимости этого модуля — они не присоединяются к глобальному объекту:
// module script (type="module")
var config = { debug: true }
console.log(globalThis.config) // undefined
Эта изоляция намеренная. Модули обеспечивают инкапсуляцию, предотвращая случайное загрязнение пространства имён между файлами. См. руководство MDN по JavaScript-модулям для более глубокого изучения.
Как var, let и const различаются на верхнем уровне
Даже в классических скриптах let и const ведут себя иначе, чем var:
// classic script
var a = 1
let b = 2
const c = 3
console.log(globalThis.a) // 1
console.log(globalThis.b) // undefined
console.log(globalThis.c) // undefined
Только var создаёт свойство глобального объекта. И let, и const создают привязки в глобальной области видимости, которые доступны во всём вашем коде, но они не становятся свойствами глобального объекта.
Это различие важно, когда сторонние скрипты ожидают найти ваши переменные в глобальном объекте, или когда вы отлаживаете код и проверяете его напрямую.
Discover how at OpenReplay.com.
Почему globalThis — это современный стандарт
Различные JavaScript-среды исторически использовали разные имена для глобального объекта:
- Браузеры используют
window - Node.js использует
global - Web Workers используют
self
Написание кроссплатформенного кода означало проверку того, какой глобальный объект существует. Стандарт globalThis решает эту проблему, предоставляя универсальную ссылку:
// Works everywhere
globalThis.sharedValue = 'accessible across environments'
Использование globalThis гарантирует, что ваш код будет корректно работать независимо от того, выполняется ли он в браузере, Node.js, Deno или Web Worker. Это правильный, платформенно-независимый подход.
Затенение переменных в глобальной области видимости
Распространённое заблуждение: вы не можете переобъявить переменную let или const, которая уже существует в глобальной области видимости. Однако вы можете затенить глобально введённые привязки var лексическими объявлениями — включая привязки var, введённые динамически (например, через eval) в современных движках:
var x = 1
{
let x = 2 // shadows the global var
console.log(x) // 2
}
console.log(x) // 1
Внутреннее let x создаёт отдельную переменную, которая временно скрывает внешнее var x внутри этого блока. Это различные привязки — изменение одной не влияет на другую.
Возможности только для модулей: await верхнего уровня
Одно практическое различие с областью видимости верхнего уровня, которое должны знать JavaScript-разработчики: await на верхнем уровне работает только в модулях:
// Only valid in modules
const data = await fetch('/api/config').then(r => r.json())
Классические скрипты не поддерживают этот синтаксис. Если вам нужен await верхнего уровня, вы должны использовать type="module" в теге script.
Лучшие практики для глобальной области видимости
- Предпочитайте модули — Они обеспечивают автоматическую изоляцию области видимости и явные импорты/экспорты
- Используйте
globalThis— Когда вам действительно нужен глобальный объект, это работает везде - Избегайте
varна верхнем уровне — Используйтеletилиconst, чтобы предотвратить непреднамеренное загрязнение глобального объекта - Будьте явными в отношении глобальных переменных — Если что-то должно быть глобальным, присоединяйте это к
globalThisнамеренно, а не полагайтесь на неявное поведение
Заключение
Глобальная область видимости в JavaScript — это не просто «переменные, доступные везде». Классические скрипты и ES-модули обрабатывают привязки верхнего уровня по-разному, и только var в классических скриптах создаёт свойства глобального объекта. Используйте globalThis для кроссплатформенной совместимости и предпочитайте модули за их встроенную инкапсуляцию. Понимание этих различий помогает писать предсказуемый код, который работает в различных JavaScript-средах.
Часто задаваемые вопросы
Да, но только если оба скрипта являются классическими скриптами и разделяют одну и ту же глобальную область видимости. Переменные доступны по имени между скриптами, но они не являются свойствами глобального объекта. Если любой из скриптов является ES-модулем, переменные остаются приватными для этого модуля, если только они не экспортированы явно.
Используйте глобальный объект, когда вам нужно делиться данными со скриптами, которые вы не контролируете, такими как сторонняя аналитика или устаревший код, который ожидает глобальные переменные. Также используйте его для полифиллов, которые должны быть универсально доступны. Для кода, который вы контролируете, предпочитайте экспорты модулей для лучшей инкапсуляции и явного отслеживания зависимостей.
globalThis поддерживается во всех современных JavaScript-средах. Если вам необходимо поддерживать устаревшие платформы, такие как Internet Explorer, используйте полифилл или полагайтесь на ваш бандлер/транспайлер для его предоставления.
Это историческое проектное решение. var был частью JavaScript с самого начала и был разработан для создания свойств глобального объекта для обеспечения совместимости. let и const были введены в ES6 с более строгими правилами области видимости, чтобы избежать подводных камней неявного загрязнения глобальной области, при этом всё ещё позволяя глобальную доступность.