Что такое замыкания в JavaScript и как они работают
Сегодня мы разберём что же такое замыкания в JavaScript и как они работают. Это тема, в которой теряются не только новички, но и даже более опытные разработчики. Поэтому предлагаю разобрать её детально.
Пример работы замыканий
Введём сначала функцию высшего порядка changeBalance(). Это функция, которая возвращает другую функцию:
Внутри объявим переменную balance, которая изначально будет равна 0:
И вернём из этой функции новую анонимную функцию, в которую мы передадим аргументом sum и в результате изменим balance на эту сумму:
Теперь посмотрим на ту магию, которая нам позволяет делать замыкание в случае такого использовании функции.
Создадим константу change, которая будет вызовом функции changeBalance(). change теперь станет функцией, которую мы описали выше. То есть функцией, которая примет sum и поменяет balance:
Давайте теперь посмотрим на её поведение, когда попытаемся вызвать эту функцию. Чтобы посмотреть на результат после изменения баланса выведем его в консоль:
Посмотрим что же будет происходить. Вызываем change и передаем ей параметры:
Вывод в консоль:
И вот тут начинает работать замыкание. Несмотря на то, что мы уже вернули функцию и она находится в глобальном контексте, мы все равно можем изменять внутри переменную balance. И более того, от вызова к вызову, наш баланс сохраняется и мы можем его менять.
Теперь на этом примере детально рассмотрим как все это работает по шагам. И как раз поймем где же находится это замыкание и что это.
Пошаговая работа замыканий
В рамках первого шага, в глобальном scope лежит функция changeBalance и константа change, а stack содержит только глобальный контекст.
Теперь начинаем исполнять код: объявляем функцию и переходим к выполнению функции changeBalance():
Что происходит на stack? Когда начинаем выполнять функцию в неё помещается changeBalance():
- changeBalance()
- global
- Global → changeBalance
То есть эта функция при выполнении объявляет баланс и возвращает новую функцию. На этом исполнение changeBalance() заканчивается.
С точки зрения scope у нас есть scope под названием changeBalance, в котором определен balance. Две вещи, которые изменились: на stack поместился changeBalance() и в scope появился changeBalance — дочерний scope относительно глобального.
Теперь перейдем к шагу три, где мы уже вызываем наш change():
- change()
- global
- Global → changeBalance
- Global → change
И тут появятся вопрос, что change никак не сможет достучаться до balance в changeBalance, потому что они находятся на одном уровне. Так почему все ещё можно изменить баланс? На самом деле он не меняет balance в changeBalance. Он меняет balance, который находится в рамках нашего замыкания. По сути, когда мы вызываем change, он как бы носит за собой контекст своего создания, и, когда происходит вызов функции, он знает что balance в нём есть. Это фактически дополнительная переменная scope, которая у него присутствует. Вот эта связь, которая реализуется внутренними механиками JavaScript и называется замыканием.
При этом, в отличие от объявлений функции, замыканиями мы никак сами управлять не можем. Это механика, которая просто работает под капотом. Более того, извне мы никак до переменной balance достучаться не сможем. Единственный, кто имеет к ней доступ — это change.
Что же такое замыкание?
Замыкание — это комбинация функции и лексического окружения, в котором эта функция была определена.
Простыми словами: функция помнит в каком контексте она была создана и может его использовать.
changeBalance является лексическим окружением, она становится неразрывной частью change.
При этом важно понимать и помнить, что замыкание всегда имеет более высокий приоритет по сравнению с переменными родительских scope. Если мы в глобальных переменных объявим еще один balance, то он изменён не будет, потому что первостепенно функция change посмотрит своё замыкание, и, если есть в этом замыкании такая переменная, она будет изменена. Если нет, то она пойдет по цепочке искать этот balance во вне.
JavaScript с нуля — основы языка и практика для начинающих
— 18 часов коротких лекций по 10 — 15 минут
— 30 упражнений для закрепления на практике
— 14 тестов для проверки знаний
— Рейтинг ⭐ 4.9 на основании отзывов
— 30-ти дневная гарантия возврата денег
Область видимости переменных, замыкание
JavaScript – язык с сильным функционально-ориентированным уклоном. Он даёт нам много свободы. Функция может быть динамически создана, скопирована в другую переменную или передана как аргумент другой функции и позже вызвана из совершенно другого места.
Мы знаем, что функция может получить доступ к переменным из внешнего окружения, эта возможность используется очень часто.
Но что произойдёт, когда внешние переменные изменятся? Функция получит последнее значение или то, которое существовало на момент создания функции?
И что произойдёт, когда функция переместится в другое место в коде и будет вызвана оттуда – получит ли она доступ к внешним переменным своего нового местоположения?
Разные языки ведут себя по-разному в таких случаях, и в этой главе мы рассмотрим поведение JavaScript.
Мы будем говорить о переменных let/const здесь
В JavaScript существует три способа объявить переменную: let , const (современные), и var (пережиток прошлого).
- В этой статье мы будем использовать переменные let в примерах.
- Переменные, объявленные с помощью const , ведут себя так же, так что эта статья и о них.
- Старые переменные var имеют несколько характерных отличий, они будут рассмотрены в главе Устаревшее ключевое слово «var».
Блоки кода
Если переменная объявлена внутри блока кода <. >, то она видна только внутри этого блока.
< // выполняем некоторые действия с локальной переменной, которые не должны быть видны снаружи let message = "Hello"; // переменная видна только в этом блоке alert(message); // Hello >alert(message); // ReferenceError: message is not defined
С помощью блоков <. >мы можем изолировать часть кода, выполняющую свою собственную задачу, с переменными, принадлежащими только ей:
Без блоков была бы ошибка
Обратите внимание, что без отдельных блоков возникнет ошибка, если мы используем let с существующим именем переменной:
// показать сообщение let message = "Hello"; alert(message); // показать другое сообщение let message = "Goodbye"; // SyntaxError: Identifier 'message' has already been declared alert(message);
Для if , for , while и т.д. переменные, объявленные в блоке кода <. >, также видны только внутри:
if (true) < let phrase = "Hello"; alert(phrase); // Hello >alert(phrase); // Ошибка, нет такой переменной!
В этом случае после завершения работы if нижний alert не увидит phrase , что и приведет к ошибке.
И это замечательно, поскольку это позволяет нам создавать блочно-локальные переменные, относящиеся только к ветви if .
То же самое можно сказать и про циклы for и while :
for (let i = 0; i < 3; i++) < // переменная i видна только внутри for alert(i); // 0, потом 1, потом 2 >alert(i); // Ошибка, нет такой переменной!
Визуально let i = 0; находится вне блока кода <. >, однако здесь в случае с for есть особенность: переменная, объявленная внутри (. ) , считается частью блока.
Вложенные функции
Функция называется «вложенной», когда она создаётся внутри другой функции.
Это очень легко сделать в JavaScript.
Мы можем использовать это для упорядочивания нашего кода, например, как здесь:
function sayHiBye(firstName, lastName) < // функция-помощник, которую мы используем ниже function getFullName() < return firstName + " " + lastName; >alert( "Hello, " + getFullName() ); alert( "Bye, " + getFullName() ); >
Здесь вложенная функция getFullName() создана для удобства. Она может получить доступ к внешним переменным и, значит, вывести полное имя. В JavaScript вложенные функции используются очень часто.
Что ещё интереснее, вложенная функция может быть возвращена: либо в качестве свойства нового объекта (если внешняя функция создаёт объект с методами), либо сама по себе. И затем может быть использована в любом месте. Не важно где, она всё так же будет иметь доступ к тем же внешним переменным.
Ниже, makeCounter создает функцию «счётчик», которая при каждом вызове возвращает следующее число:
function makeCounter() < let count = 0; return function() < return count++; // есть доступ к внешней переменной "count" >; > let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 alert( counter() ); // 2
Несмотря на простоту этого примера, немного модифицированные его варианты применяются на практике, например, в генераторе псевдослучайных чисел и во многих других случаях.
Как это работает? Если мы создадим несколько таких счётчиков, будут ли они независимыми друг от друга? Что происходит с переменными?
Понимание таких вещей полезно для повышения общего уровня владения JavaScript и для более сложных сценариев. Так что давайте немного углубимся.
Лексическое окружение
Здесь водятся драконы!
Глубокое техническое описание – впереди.
Как бы мне ни хотелось избежать низкоуровневых деталей языка, любое представление о JavaScript без них будет недостаточным и неполным, так что приготовьтесь.
Для большей наглядности объяснение разбито на несколько шагов.
Шаг 1. Переменные
В JavaScript у каждой выполняемой функции, блока кода <. >и скрипта есть связанный с ними внутренний (скрытый) объект, называемый лексическим окружением LexicalEnvironment .
Объект лексического окружения состоит из двух частей:
- Environment Record – объект, в котором как свойства хранятся все локальные переменные (а также некоторая другая информация, такая как значение this ).
- Ссылка на внешнее лексическое окружение – то есть то, которое соответствует коду снаружи (снаружи от текущих фигурных скобок).
«Переменная» – это просто свойство специального внутреннего объекта: Environment Record . «Получить или изменить переменную», означает, «получить или изменить свойство этого объекта».
Например, в этом простом коде только одно лексическое окружение:
Это, так называемое, глобальное лексическое окружение, связанное со всем скриптом.
На картинке выше прямоугольник означает Environment Record (хранилище переменных), а стрелка означает ссылку на внешнее окружение. У глобального лексического окружения нет внешнего окружения, так что она указывает на null .
По мере выполнения кода лексическое окружение меняется.
Вот более длинный код:
Прямоугольники с правой стороны демонстрируют, как глобальное лексическое окружение изменяется в процессе выполнения кода:
- При запуске скрипта лексическое окружение предварительно заполняется всеми объявленными переменными.
- Изначально они находятся в состоянии «Uninitialized». Это особое внутреннее состояние, которое означает, что движок знает о переменной, но на нее нельзя ссылаться, пока она не будет объявлена с помощью let . Это почти то же самое, как если бы переменная не существовала.
- Появляется определение переменной let phrase . У неё ещё нет присвоенного значения, поэтому присваивается undefined . С этого момента мы можем использовать переменную.
- Переменной phrase присваивается значение.
- Переменная phrase меняет значение.
Пока что всё выглядит просто, правда?
- Переменная – это свойство специального внутреннего объекта, связанного с текущим выполняющимся блоком/функцией/скриптом.
- Работа с переменными – это на самом деле работа со свойствами этого объекта.
Лексическое окружение – объект спецификации
«Лексическое окружение» – это объект спецификации: он существует только «теоретически» в спецификации языка для описания того, как все работает. Мы не можем получить этот объект в нашем коде и манипулировать им напрямую.
JavaScript-движки также могут оптимизировать его, отбрасывать неиспользуемые переменные для экономии памяти и выполнять другие внутренние действия, но при этом видимое поведение остается таким, как описано.
Шаг 2. Function Declaration
Функция – это тоже значение, как и переменная.
Разница заключается в том, что Function Declaration мгновенно инициализируется полностью.
Когда создается лексическое окружение, Function Declaration сразу же становится функцией, готовой к использованию (в отличие от let , который до момента объявления не может быть использован).
Именно поэтому мы можем вызвать функцию, объявленную как Function Declaration, до самого её объявления.
Вот, к примеру, начальное состояние глобального лексического окружения при добавлении функции:
Конечно, такое поведение касается только Function Declaration, а не Function Expression, в которых мы присваиваем функцию переменной, например, let say = function(name) <. >.
Шаг 3. Внутреннее и внешнее лексическое окружение
Когда запускается функция, в начале ее вызова автоматически создается новое лексическое окружение для хранения локальных переменных и параметров вызова.
Например, для say(«John») это выглядит так (выполнение находится на строке, отмеченной стрелкой):
В процессе вызова функции у нас есть два лексических окружения: внутреннее (для вызываемой функции) и внешнее (глобальное):
- Внутреннее лексическое окружение соответствует текущему выполнению say . В нём находится одна переменная name , аргумент функции. Мы вызываем say(«John») , так что значение переменной name равно «John» .
- Внешнее лексическое окружение – это глобальное лексическое окружение. В нём находятся переменная phrase и сама функция.
У внутреннего лексического окружения есть ссылка на внешнее outer .
Когда код хочет получить доступ к переменной – сначала происходит поиск во внутреннем лексическом окружении, затем во внешнем, затем в следующем и так далее, до глобального.
Если переменная не была найдена, это будет ошибкой в строгом режиме ( use strict ). Без строгого режима, для обратной совместимости, присваивание несуществующей переменной создаёт новую глобальную переменную с таким же именем.
Давайте посмотрим, как происходит поиск в нашем примере:
- Для переменной name , alert внутри say сразу же находит ее во внутреннем лексическом окружении.
- Когда alert хочет получить доступ к phrase , он не находит её локально, поэтому вынужден обратиться к внешнему лексическому окружению и находит phrase там.
Шаг 4. Возврат функции
Давайте вернёмся к примеру с makeCounter :
function makeCounter() < let count = 0; return function() < return count++; >; > let counter = makeCounter();
В начале каждого вызова makeCounter() создается новый объект лексического окружения, в котором хранятся переменные для конкретного запуска makeCounter .
Таким образом, мы имеем два вложенных лексических окружения, как в примере выше:
Отличие заключается в том, что во время выполнения makeCounter() создается крошечная вложенная функция, состоящая всего из одной строки: return count++ . Мы ее еще не запускаем, а только создаем.
Все функции помнят лексическое окружение, в котором они были созданы. Технически здесь нет никакой магии: все функции имеют скрытое свойство [[Environment]] , которое хранит ссылку на лексическое окружение, в котором была создана функция:
Таким образом, counter.[[Environment]] имеет ссылку на лексического окружения. Так функция запоминает, где она была создана, независимо от того, где она вызывается. Ссылка на [[Environment]] устанавливается один раз и навсегда при создании функции.
Впоследствии, при вызове counter() , для этого вызова создается новое лексическое окружение, а его внешняя ссылка на лексическое окружение берется из counter.[[Environment]] :
Теперь, когда код внутри counter() ищет переменную count , он сначала ищет ее в собственном лексическом окружении (пустом, так как там нет локальных переменных), а затем в лексическом окружении внешнего вызова makeCounter() , где находит count и изменяет ее.
Переменная обновляется в том лексическом окружении, в котором она существует.
Вот состояние после выполнения:
Если мы вызовем counter() несколько раз, то в одном и том же месте переменная count будет увеличена до 2 , 3 и т.д.
В программировании есть общий термин: «замыкание», – который должен знать каждый разработчик.
Замыкание – это функция, которая запоминает свои внешние переменные и может получить к ним доступ. В некоторых языках это невозможно, или функция должна быть написана специальным образом, чтобы получилось замыкание. Но, как было описано выше, в JavaScript, все функции изначально являются замыканиями (есть только одно исключение, про которое будет рассказано в Синтаксис «new Function»).
То есть они автоматически запоминают, где были созданы, с помощью скрытого свойства [[Environment]] , и все они могут получить доступ к внешним переменным.
Когда на собеседовании фронтенд-разработчику задают вопрос: «что такое замыкание?», – правильным ответом будет определение замыкания и объяснения того факта, что все функции в JavaScript являются замыканиями, и, может быть, несколько слов о технических деталях: свойстве [[Environment]] и о том, как работает лексическое окружение.
Сборка мусора
Обычно лексическое окружение удаляется из памяти вместе со всеми переменными после завершения вызова функции. Это связано с тем, что на него нет ссылок. Как и любой объект JavaScript, оно хранится в памяти только до тех пор, пока к нему можно обратиться.
Однако если существует вложенная функция, которая все еще доступна после завершения функции, то она имеет свойство [[Environment]] , ссылающееся на лексическое окружение.
В этом случае лексическое окружение остается доступным даже после завершения работы функции.
function f() < let value = 123; return function() < alert(value); >> let g = f(); // g.[[Environment]] хранит ссылку на лексическое окружение // из соответствующего вызова f()
Обратите внимание, что если f() вызывается много раз и результирующие функции сохраняются, то все соответствующие объекты лексического окружения также будут сохранены в памяти. В приведенном ниже коде – все три:
function f() < let value = Math.random(); return function() < alert(value); >; > // 3 функции в массиве, каждая из которых ссылается на лексическое окружение // из соответствующего вызова f() let arr = [f(), f(), f()];
Объект лексического окружения исчезает, когда становится недоступным (как и любой другой объект). Другими словами, он существует только до тех пор, пока на него ссылается хотя бы одна вложенная функция.
В приведенном ниже коде после удаления вложенной функции ее окружающее лексическое окружение (а значит, и value ) очищается из памяти:
function f() < let value = 123; return function() < alert(value); >> let g = f(); // пока существует функция g, value остается в памяти g = null; // . и теперь память очищена.
Оптимизация на практике
Как мы видели, в теории, пока функция жива, все внешние переменные тоже сохраняются.
Но на практике движки JavaScript пытаются это оптимизировать. Они анализируют использование переменных и, если легко по коду понять, что внешняя переменная не используется – она удаляется.
Одним из важных побочных эффектов в V8 (Chrome, Edge, Opera) является то, что такая переменная становится недоступной при отладке.
Попробуйте запустить следующий пример в Chrome с открытой Developer Tools.
Когда код будет поставлен на паузу, напишите в консоли alert(value) .
function f() < let value = Math.random(); function g() < debugger; // в консоли: напишите alert(value); Такой переменной нет! >return g; > let g = f(); g();
Как вы можете видеть – такой переменной не существует! В теории, она должна быть доступна, но попала под оптимизацию движка.
Это может приводить к забавным (если удаётся решить быстро) проблемам при отладке. Одна из них – мы можем увидеть не ту внешнюю переменную при совпадающих названиях:
let value = "Сюрприз!"; function f() < let value = "ближайшее значение"; function g() < debugger; // в консоли: напишите alert(value); Сюрприз! >return g; > let g = f(); g();
Эту особенность V8 полезно знать. Если вы занимаетесь отладкой в Chrome/Edge/Opera, рано или поздно вы с ней столкнётесь.
Это не баг в отладчике, а скорее особенность V8. Возможно со временем это изменится. Вы всегда можете проверить это, запустив примеры на этой странице.
Задачи
Учитывает ли функция последние изменения?
важность: 5
Функция sayHi использует имя внешней переменной. Какое значение будет использоваться при выполнении функции?
let name = "John"; function sayHi() < alert("Hi, " + name); >name = "Pete"; sayHi(); // что будет показано: "John" или "Pete"?
Такие ситуации встречаются как при разработке для браузера, так и для сервера. Функция может быть назначена на выполнение позже, чем она была создана, например, после действия пользователя или сетевого запроса.
Итак, вопрос: учитывает ли она последние изменения?
Ответ: Pete.
Функция получает внешние переменные в том виде, в котором они находятся сейчас, она использует самые последние значения.
Старые значения переменных нигде не сохраняются. Когда функция обращается к переменной, она берет текущее значение из своего или внешнего лексического окружения.
Какие переменные доступны?
важность: 5
Приведенная ниже функция makeWorker создает другую функцию и возвращает ее. Эта новая функция может быть вызвана из другого места.
Будет ли она иметь доступ к внешним переменным из места своего создания, или из места вызова, или из обоих мест?
function makeWorker() < let name = "Pete"; return function() < alert(name); >; > let name = "John"; // создаём функцию let work = makeWorker(); // вызываем её work(); // что будет показано?
Какое значение будет показано? «Pete» или «John»?
Ответ: Pete.
Функция work() в приведенном ниже коде получает name из места его происхождения через ссылку на внешнее лексическое окружение:
Таким образом, в результате мы получаем «Pete» .
Но если бы в makeWorker() не было let name , то поиск шел бы снаружи и брал глобальную переменную, что мы видим из приведенной выше цепочки. В этом случае результатом было бы «John» .
Независимы ли счётчики?
важность: 5
Здесь мы делаем два счётчика: counter и counter2 , используя одну и ту же функцию makeCounter .
Они независимы? Что покажет второй счётчик? 0,1 или 2,3 или что-то ещё?
function makeCounter() < let count = 0; return function() < return count++; >; > let counter = makeCounter(); let counter2 = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 alert( counter2() ); // ? alert( counter2() ); // ?
Ответ: 0,1.
Функции counter и counter2 созданы разными вызовами makeCounter .
Так что у них независимые внешние лексические окружения, у каждого из которых свой собственный count .
Для чего нужны замыкания (js)?
Читаю о замыканиях, хоть и не с первого раза, но немного стало понятно как это работает. Однако сразу,как у любого новичка, напросился вопрос, а для каких целей конкретно в js существуют замыкания? Объясните,пожалуйста, доступным языком.
- Вопрос задан более трёх лет назад
- 7782 просмотра
2 комментария
Простой 2 комментария
Думаю, не совсем корректный вопрос. Замыкания существуют не для каких-то целей, они просто существуют, потому что это особенности языка. Наверное стоит спросить, для каких целей используются замыкания. Хотя на этот вопрос будет непросто ответить.
Решения вопроса 1

Ну если доступным, языком, то тогда на примере.
Например, надо выводить в лог сообщение, а также номер строки и время прошедшее с момента зарузки страницы.
Если не использовать замыкание, то надо определить следующую функцию
function log(timespan, lineNumber, msg)
и две переменные
var start = Date.now(); var lineNumber = 1;
Вызываем log так
log(Date.now()-start, lineNumber++, "один"); log(Date.now()-start, lineNumber++, "два");
Очевидно, что это неудобно.
Если использовать замыкание, то пишем так
var log = (function () < // функция 1 var start = Date.now(); // текущее значение сохранятся в start var num = 1; // также используется в замыкании в функции 2. return function (msg) < // функция 2 - сохраняется в var log console.log(num++ + " " + (Date.now()-start) + " " + msg); >>)(); // () -- вызываем функцию 1
Вызываем так
log("один"); log("два");
lineNumber timespan msg ------------------------------ 1 0 один 2 1 два
Т.е. замыкание — это способ передачи данных в функцию.
Подробнее о замыканиях см Mozilla Developer Network
Примет взят отсюда
Ответ написан более трёх лет назад
Комментировать
Нравится 15 Комментировать
Ответы на вопрос 2
Замыкание это функция и внешние переменные, которые в нее приходят.
Переменная не приходит — замыкания нет.
Ответ написан более трёх лет назад
Комментировать
Нравится 1 Комментировать
Замыкания в JavaScript.
Допустим, мы вызываем внутри нашего кода какую-то функцию (назовем ее первичной функцией), т.е. какой-то код (назовем его вспомогательным), находящийся в теле этой функции. И этот вспомогательный код объявляет другую функцию (назовем ее вторичной функцией) и возвращает эту вторичную функцию в качестве возвращаемого значения.
Здесь ключевой момент– не в коде создается, а код создает. Т.е. если бы мы не вызвали код первичной функции, то вторичная функция бы не была создана. Ее и в памяти-то нигде не было бы. К ней нельзя было бы обратиться. Интерпретатор не размещает ее в памяти, пока не будет вызвана первичная функция.
В этом вспомогательном коде могут быть объявлены и инициализированы переменные, а внутри тела этой вторичной функции могут быть обращения к этим переменным, т.е. код может захватить некоторые ресурсы и ссылки на часть этих ресурсов передать в функцию.
Тогда эта возвращенная вторичная функция будет иметь такую особенность, что внутри нее будут ссылки на некие ресурсы, находящиеся где-то. И если бы эти ресурсы были удалены, то при запуске этой функции на выполнение произошла бы ошибка (были бы не найдены какие-то затребованные данные). Поэтому когда такая функция объявляется в выполняемом коде (т.е. создается динамически) и при возвращении из кода присваивается какой-то переменной, то чтобы эта операция имела смысл, интерпретатор сохраняет в памяти созданные в коде данные, на которые ссылается эта функция.
Именно память под данные, на которые ссылается вторичная функция, выделенная в момент выполнения кода первичной функции (в этот момент как раз создается и размещается в памяти и вторична функция) сохраняется. Остальная память, временно выделявшаяся при работе кода первичной функции, высвобождается.
Т.е. эту вторичную функцию можно рассматривать просто как обычный объект, содержащий в своих полях некоторые данные. Естественно, пока на этот объект где-то будут оставаться ссылки, будут сохраняться в памяти и данные этого объекта.
Пока где-то в коде существует действующая ссылка на эту вторичную функцию, будут работоспособными и все ссылки внутри этой функции на участки памяти с данными, которые выделялись при создании этой вторичной функции.
Если мы второй раз вызовем первичную функцию, мы создадим уже совершенно другой объект вторичной функции, со своими данными.
Эта вторичная функция, т.е. этот динамически созданный объект, называется, вы не поверите, — замыканием.
Ответ написан более двух лет назад
Комментировать
Нравится 1 Комментировать
Ваш ответ на вопрос
Войдите, чтобы написать ответ

- JavaScript
- +2 ещё
Как перевести тип данных с С++ на JS?
- 1 подписчик
- 11 минут назад
- 18 просмотров
Что такое замыкание в программировании
Сегодня разбираем полезное понятие из мира программирования — замыкание. Это нужно тем, кто хочет серьёзно заниматься разработкой и говорить со старшими товарищами на одном языке.
Для начала потребуется два термина: область видимости и стек. Если нужно вспомнить, раскрывайте:
Что такое область видимости
Область видимости определяет, к каким переменным функция, команда или другая переменная может получить доступ, а к каким нет. Проще говоря, что каждая функция «видит» — видит ли она те переменные и объекты, которые созданы за её пределами? Видит ли она то, что вложено в функции, которые запускаются внутри неё?
Обычно так: если переменная объявлена в самой программе, к ней можно получить доступ из вложенной функции. А если объявить переменную внутри функции, то не обратиться к ней извне не получится.
Что такое стек
Стек — это как список задач при выполнении программы. Сначала компьютер исполняет один код, внутри которого появляется какая-то функция — это как будто отдельная программа. Компьютер откладывает текущую программу, делает себе в стеке пометку «вернуться сюда, когда доделаю новую программу» и исполняет эту новую функцию. Исполнил — смотрит в стек, куда вернуться. Возвращается туда.
Про стек нужно думать, потому что он не безразмерный. Нельзя бесконечно вкладывать функции друг в друга.
Что такое замыкание
Замыкание в программировании — это когда одна функция возвращает как результат своей работы не переменную, а другую функцию. При этом хитрость в том, что внутренняя функция имеет доступ к переменным из внешней функции и может с ними работать в любой момент.
Чаще всего это используют, чтобы сделать переменную, которая на самом деле работает как функция.
Звучит сложно, объясним на примерах.

Пример замыкания в коде
Сделаем функцию, которая вернёт нам фразу «Привет, …» вместе с именем, которое мы в него отправим. Но сделаем это через замыкание — чтобы посмотреть, как именно всё устроено.
Обратите внимание на две переменные в конце: mike и maxim. По сути, эти переменные — ссылки на вызов результата функции hello(), но с конкретным параметром.
// внешняя функция function hello(name) < // внутренняя функция-замыкание // возвращаем её как результат работы внешней return function() < // внутренняя функция выводит сообщение на экран console.log("Привет, " + name); >> // создаём новые переменные, используя замыкание mike = hello("Миша"); maxim = hello("Максим") // запускаем внутреннюю функцию с нашими параметрами, просто указав имена переменной mike(); maxim();

Ещё один пример, посложнее
Только что мы сделали просто — привязали сообщение к переменной без возможности на него повлиять. Но можно создавать такие переменные, у которых могут быть свои параметры — и в них уже передавать любые значения.
Для примера сделаем замыкание и заведём две переменные — каждая будет выдавать сообщение со своим началом фразы, а продолжение будем передавать им в виде параметра:
// внешняя функция, у которой есть свой параметры function greeting(hi) < // внутренняя функция-замыкание, тоже со своим параметром // возвращаем её как результат работы внешней return function(name) < // внутренняя функция выводит сообщение на экран console.log(hi + ", " + name); >> // создаём новые переменные, используя замыкание morning = greeting("Доброе утро"); thnx = greeting("Спасибо за комментарий") // запускаем внутреннюю функцию с нашими параметрами, указав параметры вызова morning("коллеги"); thnx("Павел");

Причём здесь область видимости
Из последнего примера видно, что объявление переменных происходит так:
- переменной присваивается результат выполнения внешней функции;
- этот результат зависит от параметра, который мы ей передали;
- потом, при обращении к переменной, происходит обработка только второго параметра, а первый хранится где-то в памяти и подставляется в нужный момент.
Но как такое может быть, когда внутренняя функция использует переменную из области видимости внешней функции, которой уже нет?
Всё дело в том, что при замыкании создаётся как бы виртуальное окружение, в котором и хранится значение из внешней функции. А вот как это работает с точки зрения области видимости:

Зачем нужны замыкания
На замыканиях строится около половины алгоритмов в функциональном программировании. А ещё на них можно построить много разного:
- изолировать логику выполнения фрагментов кода, если это не позволяют сделать встроенные возможности языка (как в JavaScript);
- лучше структурировать код, особенно при организации функций, которые отличаются только несколькими элементами;
- реализовать инкапсуляцию в тех языках, где её нет.
Что дальше
Следующий шаг — попробовать замыкания в деле. Напишем небольшой код и проверим, как работают замыкания и для чего они могут пригодиться.
