Изначально в начале конспекта и урока речь шла о глобальном и локальном окружении, но объяснялась фактически глобальная и локальная область видимости. В видео эта тема до сих пор называется «глобальное и локальное окружение», текстовая версия урока исправлена.
Прежде, чем приступить к уроку, разберёмся с терминологией:
Путаница возникает ещё потому, что в разделе про Область видимости мы смотрим на примеры, которые работают по правилу «лексическая область видимости». От этого никуда не деться, так как язык JavaScript работает именно так.
Давайте поговорим об окружении. Наша планета огромна, но мы все делим её. Если вы построите химический завод, неплохо бы изолировать его от окружающего мира, чтобы то, что в нём происходит оставалось внутри. Вы можете сказать, что в этом здании своё окружение, микроклимат, изолированный от внешней окружающей среды. В программировании такая изоляция называется областью видимости.
Ваша программа имеет подобную структуру по похожим причинам. То, что вы создаёте снаружи — снаружи функций, инструкций if, циклов и других блоков кода — находится во внешней, глобальной области видимости.
const age = 29;
const multiplier = (num) => {
const x = 10;
return num * x;
}
let result = true;
Константа age
, функция multiplier
и переменная result
— имеют "глобальную область видимости". Область видимости означает "область, где компоненты доступны".
Внутри функции multiplier
есть константа x
. Поскольку она внутри блока кода, это локальная константа, а не глобальная. Она видна только внутри этой функции, но не снаружи. У неё локальная область видимости.
В функции multiplier
есть ещё один компонент из локальной области видимости — аргумент num
. Он не задан так же чётко, как константы или переменные, но ведёт себя почти как локальная переменная.
У нас нет доступа к x
снаружи, как будто её там не существует:
const multiplier = (num) => {
const x = 10;
return num * x;
}
console.log(x); // ReferenceError: x is not defined
console.log
вызывается в глобальном окружении, а x
не задан в этом глобальном окружении. Поэтому мы получаем Reference Error.
Мы можем задать x
глобально:
const x = 55;
const multiplier = (num) => {
const x = 10;
return num * x;
}
console.log(x); // 55
Теперь существует глобальный x
и его значение было выведено на экран, но локальный x
внутри функции multiplier
по-прежнему виден только внутри этой функции. Эти два x
не имеют ничего общего друг с другом, они находятся в разных областях видимости. Они не схлопываются в одно целое, несмотря на то, что у них одно и то же имя.
Любой блок кода между фигурными скобками имеет локальную область видимости. Вот пример с блоком if
:
let a = 0;
if (a === 0) {
const local = 10;
}
console.log(local); // => ReferenceError: local is not defined
То же работает для циклов while
и for
.
Ок, локальное не доступно снаружи. Но глобальное доступно везде. Даже внутри чего-то? Да!
let a = 0;
const changer = () => {
a++;
}
console.log(a); // 0
changer();
console.log(a); // 1
Эта глобальная переменная a
изменилась внутри функции changer
. Функция вообще выполняет что-то только когда её вызывают, а не когда её определяют, так что вначале a
это 0
, но после того как вызывается changer
, a
становится 1
.
Хоть это и заманчиво всё помещать в глобальную область видимости и забыть о сложностях областей видимости — это ужасная практика. Глобальные переменные делают ваш код невероятно хрупким. В таком случае что угодно может сломать что угодно. Поэтому избегайте глобальной области видимости, храните вещи там, где им место.
Взгляните на эту программу:
let a = 7;
let b = 10;
const multiplier = () => {
let a = 5;
return a * b;
}
multiplier(); // 50
Функция multiplier
возвращает произведение a
и b
. a
задано внутри, а b
— нет.
Пытаясь решить умножение a * b
, JavaScript ищет значения a
и b
. Он начинает искать локально и выходит наружу, по одной области видимости за шаг, пока он не найдёт то, что ему нужно или пока не поймёт, что это невозможно найти.
Поэтому в данном примере JavaScript начинает с поиска a
внутри локальной области видимости — внутри функции multiplier
. Он находит значение сразу и переходит к b
. Невозможно найти значение b
в локальной области видимости, поэтому он переходит к наружной области. Тут он находит b
— это 10. a * b
превращается в 5 * 10
, а затем в 50
.
Весь этот кусок кода мог бы быть внутри другой функции, и ещё внутри другой функции. И если бы b
не нашлась здесь, JavaScript продолжил бы искать b
за пределами функции, слой за слоем.
Заметьте, что a = 7
не затронула вычисления, a
была найдена внутри, поэтому внешняя a
не сыграла роли.
Этот механизм называется лексической областью видимости. Область видимости любого компонента определяется местом расположения этого компонента внутри кода. И вложенные блоки имеют доступ к их внешним областям видимости.
Большинство языков программирования имеют что-то вроде области видимости или окружения, и этот механизм позволяет существовать замыканиям. Замыкание — это всего лишь модное название функции, которая запоминает внешние штуки, используемые внутри.
Перед тем, как мы продолжим, давайте вспомним, как функции создаются и используются:
const f = () => {
return 0;
}
f
— довольно бесполезная функция, она всегда возвращает 0. Весь этот набор состоит из двух частей: константы и самой функции.
Важно помнить, что эти два компонента раздельны. Первый — константа с именем f
. Её значение могло бы быть числом или строкой. Но в данном случае её значение — функция.
Мы использовали аналогию в предыдущих уроках: константы как листы бумаги — имя на одной стороне, значение на другой. Следовательно, f
— лист бумаги с f
на одной стороне и описанием запускаемой функции на другой.
Когда вы вызываете эту функцию, вот так:
f();
… создаётся новый ящик, основываясь на описании на этом листе бумаги.
Ок, вернёмся к замыканиям. Рассмотрим следующий код:
const createPrinter = () => {
const name = "King";
const printName = () => {
console.log(name);
}
return printName;
}
const myPrinter = createPrinter();
myPrinter(); // King
Функция createPrinter
создаёт константу name
и затем функцию с именем printName
. Обе они локальные для функции createPrinter
, и доступны только внутри createPrinter
.
У самой printName
нет локальных компонентов, но у неё есть доступ к области видимости, где она сама находится, внешней области, где задана константа name
.
Затем функция createPrinter
возвращает функцию printName
. Помните, определения функций — это описания запущенных функций, просто фрагменты информации, как числа или строки. Поэтому мы можем вернуть определение функции, как мы возвращаем число.
Во внешней области видимости мы создаём константу myPrinter
и задаём ей значение, которое возвращает вызов функции createPrinter()
. Этот вызов возвращает функцию, так что теперь myPrinter
— это функция. Вызовите её, и на экран выведется "King".
Тут есть странная штука: эта константа name
была создана внутри функции createPrinter
. Функция была вызвана и исполнена. Как мы знаем, когда функция заканчивает работу, она больше не существует. Этот магический ящик исчезает со всеми своими внутренностями.
НО он возвратил другую функцию, и уже она как-то запомнила константу name
. Поэтому когда мы вызывали myPrinter()
, она вывела "King" — запомненное значение, при том, что область видимости, где оно было задано больше не существует.
Функция, которая была возвращена из createPrinter
, называется замыканием. Замыкание — это сочетание функции и окружения, где она была задана. Функция "замкнула" в себе некоторую информацию из области видимости.
Это может выглядеть как JavaScript-фокус, но замыкания, когда их используют разумно, могут сделать код приятней, чище и проще для чтения. И сама идея возврата функций тем же способом, которым можно возвращать числа и строки, даёт больше возможностей и гибкости.
Вы заметите, как часто эти идеи используются в программировании, и мы рассмотрим их мощь в следующих курсах.
{ }
задают новую локальную область видимостиЛокальные константы и переменные не видимы снаружи их области видимости:
const multiplier = (num) => {
const x = 10;
return num * x;
}
console.log(x); // ReferenceError: x is not defined
Но если x
представлен глобально, то он доступен:
const x = 55;
const multiplier = (num) => {
const x = 10;
return num * x;
}
console.log(x); // 55
Возможен доступ к внешней области видимости:
let a = 0;
const changer = () => {
a++;
}
console.log(a); // 0
changer();
console.log(a); // 1
Функция фактически производит что-то, только когда она вызывается, а не задаётся, поэтому изначально a
это 0
, но после того, как вызвана changer
, a
становится 1
.
JavaScript пытается найти значение в текущем окружении. Но значение не находится и JavaScript выходит наружу, на один уровень за попытку, пока не найдёт значение или не поймет, что значение невозможно найти.
let a = 7;
let b = 10;
const multiplier = () => {
let a = 5;
return a * b;
}
multiplier(); // 50
Здесь, в выражении a * b
, функция multiplier
использует локальную a
(потому что она обнаружена локально), и наружную b
(потому что локально b
найдена не была).
const createPrinter = () => {
const name = "King";
const printName = () => {
console.log(name);
}
return printName;
}
const myPrinter = createPrinter();
myPrinter(); // King
myPrinter — это функция, которая была возвращена createPrinter
. Несмотря на то, что вызов createPrinter
окончен и константы name
больше не существует, значение запомнено в myPrinter
.
Это замыкание: сочетание функции и окружения, где она была заявлена.
Вам ответят команда поддержки Хекслета или другие студенты.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт