JS: Функции
Теория: Область видимости и замыкания
Если вы построите химический завод, неплохо бы изолировать его от окружающего мира, чтобы не произошла утечка химикатов. Можно сказать, что в этом здании свое окружение — микроклимат, изолированный от внешней окружающей среды.
В программировании такая изоляция называется областью видимости. В этом уроке мы поговорим об окружении. Вы узнаете, на какие зоны делятся программы и как это влияет на работу кода.
Области видимости
С точки зрения окружения, программы делятся на две области видимости:
- Глобальную (внешнюю)
- Локальную (внутреннюю)
В глобальной области видимости находится все, что мы создаем снаружи функций, инструкций if, циклов и других блоков кода:
В этом примере константа age, функция multiplier и переменная result имеют глобальную область видимости.
Поговорим подробнее о локальной зоне видимости из этого примера. Внутри функции multiplier есть константа x. Она находится внутри блока кода, она видна только внутри этой функции.
В функции multiplier есть еще один компонент из локальной области видимости — аргумент num. Он не задан так же четко, как константы или переменные, но ведет себя почти как локальная переменная.
У нас нет доступа к x снаружи, как будто ее там не существует:
В этом примере console.log вызывается в глобальном окружении, но при этом x не задан глобально. Поэтому мы получаем сообщение Reference Error.
Попробуем задать x глобально:
Теперь существует глобальный x. Мы вывели его значение на экран, но локальный x внутри функции multiplier по-прежнему виден только внутри этой функции.
Эти два x не имеют ничего общего друг с другом, они находятся в разных областях видимости. Они не схлопываются в одно целое, несмотря на одинаковое имя.
Любой блок кода между фигурными скобками имеет локальную область видимости. Вот пример с блоком if:
То же работает для циклов while и for.
Теперь мы знаем, что локальное недоступно снаружи, а глобальное — доступно везде. Даже внутри чего-то? Да, и это видно в примере ниже:
Эта глобальная переменная a изменилась внутри функции changer. Функция выполняет что-то, только когда ее вызывают, а не когда ее определяют. Поэтому вначале a это 0, но после вызова changer переменная a становится 1.
Может показаться, что было бы удобно все поместить в глобальную область видимости и забыть о сложностях. На самом деле, это ужасная практика. Глобальные переменные делают код невероятно хрупким. В таком случае может сломаться все что угодно. Поэтому избегайте глобальной области видимости — храните вещи там, где им место.
Лексическая область видимости
Кроме глобальной и локальной, существует еще и лексическая область видимости. Это конкретный механизм, одно из правил для области видимости, которое применяется в JavaScript и большинстве других языков.
Под лексической областью видимости можно понимать просто механизм поиска значений: смотрим в текущей области, если нет — идем на уровень выше, и так далее. Слово «лексический» означает, что видимость задается исключительно текстом программы.
Другими словами, мы можем, посмотреть на текст программы и узнать область видимости в любой точке. В других языках может быть не лексический механизм, а динамический.
Разберемся, как лексическая область работает на практике. Взгляните на эту программу:
Функция multiplier возвращает произведение a и b. Значение a задано внутри, а b — нет.
Пытаясь решить умножение a * b, JavaScript ищет значения a и b. Он начинает искать локально и выходит наружу по одной области видимости за шаг. Так происходит до тех пор, пока JavaScript:
- Не найдет то, что нужно
- Не увидит, что это невозможно найти
В этом примере JavaScript начинает с поиска a внутри локальной области видимости — внутри функции multiplier. Он находит значение сразу и переходит к b. Невозможно найти значение b в локальной области видимости, поэтому он переходит к внешней области. Тут он находит b — это 10. Таким образом a * b превращается в 5 * 10, а затем — в 50.
Весь этот кусок кода может быть внутри другой функции, и еще внутри другой функции. Если бы b не нашлась здесь, JavaScript продолжил бы искать b за пределами функции, слой за слоем.
Заметьте, что a = 7 не затрагивает вычисления. Значение a нашлось внутри, поэтому внешняя a не сыграла роли.
Этот механизм называется лексической областью видимости. Область видимости любого компонента определяется его расположением внутри кода. И вложенные блоки имеют доступ к их внешним областям видимости.
Замыкание
Большинство языков программирования имеют что-то вроде области видимости или окружения, и этот механизм позволяет существовать замыканиям.
Замыкание — это сочетание функции и окружения, где она была заявлена. Другими словами, это всего лишь новое название функции, которая запоминает внешние штуки, используемые внутри.
Давайте вспомним, как функции создаются и используются:
Функция f довольно бесполезная, она всегда возвращает 0. Весь этот набор состоит из двух частей:
- Константы
- Самой функции
Важно помнить, что эти два компонента раздельны. Первый — константа с именем f. Ее значение могло бы быть числом или строкой, но здесь это функция.
Когда мы вызываем эту функцию, это выглядит вот так:
Вернемся к замыканиям и рассмотрим следующий код:
Функция createPrint создает константу name и затем функцию с именем printName. Созданные функция и константа локальны — они доступны только внутри createPrint.
У самой printName нет локальных компонентов. Но у нее есть доступ к своей области видимости — то есть к внешней области, где задана константа name.
Затем функция createPrint возвращает функцию printName. Помните, что определения функций — это описания запущенных функций, просто фрагменты информации, как числа или строки. Поэтому мы можем вернуть определение функции, как мы возвращаем число.
Во внешней области видимости мы создаем константу myPrint и задаем ей значение, которое возвращает вызов функции createPrint(). Этот вызов возвращает функцию, так что теперь myPrint — это функция. Вызовите ее, и на экран выведется King.
Тут есть одна странная штука: эта константа name была создана внутри функции createPrint. Функция была вызвана и исполнена. Как мы знаем, когда функция заканчивает работу, она больше не существует.
Этот магический ящик исчезает со всеми своими внутренностями, но он возвращает другую функцию, и уже она запоминает константу name. Поэтому когда мы вызывали myPrint(), она вывела King — запомненное значение. При этом больше не существует та область видимости, где мы задали это значение.
Функция, которую мы вернули из createPrint — это и есть замыкание. Другими словами, это сочетание функции и окружения, где она была задана. Функция замкнула в себе некоторую информацию из области видимости.
Если использовать их разумно, замыкания могут сделать код приятней, чище и проще для чтения. Даже сама идея возврата функций тем же способом, которым можно возвращать числа и строки, дает больше возможностей и гибкости.
Вы заметите, как часто эти идеи используются в программировании. Мы рассмотрим их потенциал в следующих курсах.




