JavaScript - Что такое замыкание? Подробнее про замыкания в JavaScript Замыкания javascript.


В JavaScript функции могут быть описаны не только одна за другой, но и одна внутри другой. Когда у вас одна функция находится внутри другой, то внутренняя фунция имеет доступ к переменным внешней функции.

Function внешняя(x) { var tmp = 3; function внутренняя(y) { alert(x + y + (++tmp)); // выведет 16 } внутренняя(10); } внешняя(2);

Этот код всегда выдаёт 16, потому, что функция внутренняя видит x , который является переменной в функуции внешняя. В данном случае аргументом функции. Так же внутренняя() может видить tmp из внешней() .

Это и называется замыкание или closure. Если точнее, замыканием называется именно внешняя функция, а всё что внутри неё называется closure environment или среда замыкания.

Иногда говорят, что замыкание это функция которая возвращает функцию, это неправильно, для того чтобы назвать функцию замыканием достаточно того чтобы внутренняя функция обращалась к переменной извне своей области видимости.

Function foo(x) { var tmp = 3; return function (y) { alert(x + y + (++tmp)); // will also alert 16 } } var bar = foo(2); // bar is now a closure. bar(10);

Приведённая выше функция также выдаст 16, поскольку bar даже после завершения foo продолжает иметь доступ к x и tmp , пусть даже сама переменная bar и не находится внутри области видимости в которой они были объявлены.

При этом, поскольку переменная tmp всё ещё находится внутри замыкания bar , она продолжает увеличиваться при каждом вызове bar .

Вот простейший пример замыкания:

Var a = 10; function test() { console.log(a); // вывод 10 console.log(b); // вывод 6 } var b = 6; test();

При запуске функции в JavaScript, для неё создаётся окружение, то есть список всех видимых ей переменных, не только аргументов и переменных объявленных внутри неё, но и снаружи, в данном примере это "a" и "b".

Можно создать более чем одно замыкание в одном окружении, вернув их массивом, объектом или привязав к глобальным переменным. В таком случае, все они будут работать с тем же самым значением x или tmp , не создавая отдельных копий.

Поскольку в нашем примере x это число, то его значение копируется в foo как его аргумент x .

С другой стороны, в JavaScript всегда используются ссылки, когда передаются объекты. Если бы вы вызвали foo с объектом в качестве аргумента, то возвращённое замыкание вернуло бы ссылку на оригинальный объект!

Function foo(x) { var tmp = 3; return function (y) { alert(x + y + tmp); x.memb = x.memb ? x.memb + 1: 1; alert(x.memb); } } var age = new Number(2); var bar = foo(age); // bar теперь замыкание ссылающееся на age. bar(10);

Как и следовало ожидать, каждый вызов bar(10) увеличивает x.memb . Чего вы могли не ожидать, так это, что x продолжает ссылаться на тот же самый объект, что и age ! После двух вызовов bar , age.memb будет равен 2! Кстати, так и происходят утечки памяти в HTML объектах.

Доброго времени суток, гики веб-разработки. Сегодня мы углубим ваши знания языка и разберем замыкания в JavaScript. Это очень важный, ключевой раздел при изучении JS, без которого по сути и «каши не сваришь».

Поэтому в текущей публикации мы пройдемся с вами по основным моментам замыкания для того, чтобы не быть чайниками. Я объясню, что это такое и с какими ошибками можно столкнуться. Также приведу несколько примеров для лучшего понимания материала. Как говорится: «Меньше слов, больше дела». Так что за дело!

Что подразумевает под собой замыкание

В JavaScript замыкание – это обычные вложенные функции, которые используют свободные (независимые) переменные, находящиеся в их окружении.

Такое окружение называется лексической областью видимости – Lexical scoping . Если более простыми словами, то это механизм, который позволяет вложенным функциям использовать переменные, объявленные вовне их тела, и «замыкать» последние на себе.

Рассмотрим пример. В коде создается функция с названием IntCounter () , в которой объявляется локальная переменная calls и вложенная функция. Последняя должна возвращать количество вызовов в основном коде.

1 2 3 4 5 6 7 8 9 10 function IntCounter() { var calls = 0; return function() { return ++calls; } } var CountСalls = IntCounter (); CountСalls(); //1 CountСalls(); //2 CountСalls(); //3

function IntCounter() { var calls = 0; return function() { return ++calls; } } var CountСalls = IntCounter (); CountСalls(); //1 CountСalls(); //2 CountСalls(); //3

В этом случае вложенной функции доступна локальная переменная внешней. И в добавок к этому данная переменная продолжает существовать и быть доступной для вложенной, несмотря на завершение выполнения основной функции.

Именно поэтому в прикрепленном выше примере переменная calls продолжает свое существование и сохраняет последнее присвоенное значение.

Почему данный механизм возможен?

Вот тут будьте внимательны!!! Попытайтесь хорошенько разобрать и запомнить прочитанное. В будущем это поможет вам понимать более сложные вещи в .

Итак, ссылки на внешние переменные, объекты хранятся во внутреннем свойстве вложенной функции под названием [[ Scope]] . Это скрытое свойство, которое присваивается функциям при их создании и ссылается на их Lexical scoping .

[[ Scope]] привязывается к конкретной функции и таким образом создает связь между ней и ее местом рождения. Значение [[ Scope]] сохраняется и поэтому в примерах выше была возможность получить последнее значение и увеличить его.

Практическое применение замыканий

Данная штука очень полезна на практике. Особенно часто к ней обращаются в веб-приложениях, в которых используется множество событий.

Так как замыкания позволяют простым способом связать некоторые действия, переменные, параметры и сохранять значения после отработки методов, то они значительно упрощают написание определенных задач во время их программной реализации.

Для примера можно привести тривиальную задачу, которая иногда встречается при программировании онлайн-библиотек, блогов со статьями или других , где можно читать какую-либо литературу. Ваш сервис предлагает пользователю прочтение каких-то книг.

После открытия определенного источника для юзера стандартный шрифт может оказаться слишком мелким или наоборот слишком крупным. Поэтому ему предлагается выбрать размер символов.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 Выберите размер шрифта
12 16 20

Том первый. Глава вторая.

Практический пример использования замыкания body { font-family: Arial, sans-serif; font-size: 14px; } h1 { font-size: 1.5em; } h2 { font-size: 1.2em; } Выберите размер шрифта
12 16 20

Здесь написан какой-то текст исторического романа.

Том первый. Глава вторая.

Продолжение увлекательной истории...

function ChangeSize(newSize) { return function() { document.body.style.fontSize = newSize + "px"; }; } var size12 = ChangeSize(12); var size16 = ChangeSize(16); var size20 = ChangeSize(20); document.getElementById("size_12").onclick = size12; document.getElementById("size_16").onclick = size16; document.getElementById("size_20").onclick = size20;

Наиболее распространенные ошибки

К частым ошибкам можно отнести замыкание в цикле. Для лучшего понимания представьте, что у вас имеется программа, в которую вы вносите изучаемые вами английские слова. Для проверки правильности перевода, подсказка выводится при наведении на конкретное слово.

Обычно новички пишут код следующим образом:

Помощник при изучении английских слов

Наведите на слово для получения перевода.

elections

electricity

electric

function showTranslation (translation) { document.getElementById("help").innerHTML = translation; } function DictionaryHelp() { var helpText = [ {"id": "1", "help": "Выборы"}, {"id": "2", "help": "Электричество"}, {"id": "3", "help": "Электрический"} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onmouseover = function() { showTranslation (item.help); } } } DictionaryHelp();

Однако при запуске программы и наведении на любое из слов, ответ всегда будет один и тот же – перевод слова «электрический».

Почему же так? На самом деле все функции, выполняющиеся как обработчики событий, реализуют все то же замыкание. Поэтому в коде было создано целых три замыкания (так как элементов в цикле всего три).

Однако все они ссылаются на одно и то же окружение, ведь ко времени выполнения события цикл уже был пройден и конечным обработанным элементом остался последний в .

Для решения этой проблемы в новых версиях (начиная с ECMAScript 6 ) можно использовать ключевое let . В других ситуациях следует обратиться за помощью к function factory .

Для реализации такого подхода стоит написать дополнительную функцию, которая позволит создать три отдельные лексические области видимости.

Скрипт изменится следующим образом:

function showTranslation(translation) { document.getElementById("help").innerHTML = translation; } function HelpCallback(help) { return function() { showTranslation(help); }; } function DictionaryHelp() { var helpText = [ {"id": "1", "help": "Выборы"}, {"id": "2", "help": "Электричество"}, {"id": "3", "help": "Электрический"} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onmouseover = HelpCallback(item.help); } } DictionaryHelp();

Это практическое руководство по работе с замыканиями в JavaScript

Замыкания - это базовая концепция JavaScript, которая сбивает с толку многих новичков, тем не менее её должен знать и понимать каждый разработчик.

Правильное представление о замыканиях поможет вам писать более эффективный и «чистый» код, чтобы стать отличным JavaScript разработчиком.

В этой статье я попробую объяснить, как устроены замыкания и как они работают в JavaScript.

Начнём без промедлений 🙂

Что такое замыкание?

Замыкание - это функция, которая имеет доступ к своему внешнему окружению, даже после того, как внешняя функция возвращена. Другими словами - замыкание помнит и имеет доступ к переменным и аргументам внешней функции, даже после её завершения.

Перед тем как продолжить, давайте разберёмся с лексической областью видимости.

Что такое лексическая область видимости?

Лексическая или статическая область видимости в JavaScript относится к понятиям доступности переменных, функций и объектов в зависимости от их физического расположения в исходном коде. Пример:

Let a = "global"; function outer() { let b = "outer"; function inner() { let c = "inner" console.log(c); // prints "inner" console.log(b); // prints "outer" console.log(a); // prints "global" } console.log(a); // prints "global" console.log(b); // prints "outer" inner(); } outer(); console.log(a); // prints "global"

Здесь, функция inner имеет доступ к переменным, определённым в её собственной области видимости, а также в функции outer и глобально. И функция outer имеет доступ к переменным, определённым в собственном пространстве видимости и глобально.

Иерархия областей видимости в этом коде выглядит так:

Global { outer { inner } }

Обратите внимание, что функция inner окружена лексической областью видимости функции outer , которая в свою очередь окружена глобальной областью видимости. Вот почему функция inner может получить доступ к переменным, определённым в функции outer , а также в глобальном пространстве.

Практические примеры замыкания

Давайте рассмотрим практические примеры замыканий, перед тем как начнем разбираться в их устройстве.

Пример №1

В этом коде мы вызываем функцию person , которая возвращает внутреннюю функцию displayName и сохраняет её в переменной peter . Когда мы вызываем функцию peter (она ссылается на функцию displayName), в консоли выводится имя ‘Peter’.

Обратите внимание, что в функции displayName нет переменной name , т.е. эта функция как-то получает доступ к своей внешней функции person , даже после того, как та функция возвращена. Поэтому функция displayName и является замыканием.

Пример №2

И снова мы сохраняем анонимную внутреннюю функцию, возвращённую функцией getCounter в переменную count . Так как теперь функция count является замыканием, у неё есть доступ к переменной counter функции getCounter , даже после завершения getCounter() .

Обратите внимание, что значение counter не сбрасывается на 0 при каждом вызове функции count , как это обычно бывает.

Так происходит потому, что при каждом вызове count() для неё создаётся новая область видимости. Но для функции getCounter существует только одна область видимости, потому что переменная counter определена в пространстве getCounter() . Её значение будет увеличиваться при каждом вызове функции count , а не обнуляться.

Как работают замыкания?

Мы говорили о том, что такое замыкания и как они применяются на практике. Теперь давайте разберёмся, как они работают в JavaScript.

Чтобы в полной мере понять, как работают замыкания в JavaScript, необходимо знать два наиболее важных понятия: 1) контекст исполнения и 2) лексическое окружение.

Контекст исполнения

Это абстрактное окружение, где код JavaScript оценивается и исполняется. Когда глобальный код выполняется, это происходит в глобальном контексте, а код функции выполняется в контексте функции.

В текущий момент может быть только один контекст исполнения (потому что JavaScript - однопоточный язык). Этот процесс управляется структурой данных стека, известным как Execution Stack или Call Stack.

Execution Stack - это стек со структурой LIFO (Last in, first out), в котором элементы могут быть добавлены или удалены только с верху стека.

Текущий контекст исполнения всегда находиться в верхней части стека. После выполнения текущей функции, её контекст исполнения «вылетит» из стека, а контроль перейдёт к следующему, под ним в стеке.

Давайте разберём фрагмент кода, чтобы лучше понимать контекст исполнения и стек:

После выполнения этого кода, движок JavaScript создаёт глобальный контекст исполнения, чтобы выполнить глобальный код. Когда JS встречает вызов функции first() , он создаёт новый контекст исполнения для этой функции, и «проталкивает» его на верх стека.

Стек исполнения для этого кода выглядит вот так:

Когда функция first() завершена, она удаляется из стека. Управление переходит к следующему контексту, в этом случае, к глобальному контексту исполнения. Оставшийся код будет выполнен в глобальном пространстве.

Лексическое окружение

Каждый раз, когда движок JavaScript создаёт контекст исполнения, чтобы выполнить функцию или глобальный код, он также создаёт новое лексическое окружение для хранения переменных, определённых в этой функции в процессе её исполнения.

Лексическое окружение - это структура данных, которая содержит карту соответствий идентификатор-переменная . В ней идентификатор ссылается на имя переменной/функции, а переменная на сам объект (включая функциональный объект) или на примитивное значение.

В лексическом окружении есть два компонента: 1) запись о внешних условиях и 2) ссылка на внешнюю среду.

1. Запись о внешних условиях  - это фактическое место, где хранятся объявления переменных и функций.

Концептуально лексическое окружение выглядит так:

LexicalEnvironment = { environmentRecord: { : , : } outer: < Reference to the parent lexical environment> }

Давайте ещё раз посмотрим на предыдущий фрагмент кода:

Let a = "Hello World!"; function first() { let b = 25; console.log("Inside first function"); } first(); console.log("Inside global execution context");

Когда движок JavaScript создаёт глобальный контекст исполнения, для выполнения глобального кода, он также создаёт новое лексическое окружение в глобальном пространстве. Лексическое окружение для глобального пространства выглядит так:

GlobalLexicalEnvironment = { environmentRecord: { a: "Hello World!", first: < reference to function object > } outer: null }

Здесь для лексического окружения установлен null , потому что нет внешнего лексического окружения для глобального пространства.

Когда движок создаёт контекст исполнения для функции first() , он также создаёт лексическое окружение для хранения переменных, определённых в процессе выполнения функции. Лексическое окружение функции выглядит так:

FunctionLexicalEnvironment = { environmentRecord: { b: 25, } outer: }

Для внешнего лексического окружения функции установлено глобальное лексическое окружение, потому что функция окружена глобальным пространством в исходном коде.

Примечание

После выполнения функции, её контекст исполнения удаляется из стека. Но удалится ли её лексическое окружение из памяти, зависит от того, ссылается ли на него другое лексическое окружение, в свойствах их внешнего окружения.

Примеры замыканий. В деталях

Теперь, когда мы разобрались с контекстом исполнения и лексическим окружением, вернёмся к замыканиям.

Пример №1

Разберём этот код:

Function person() { let name = "Peter"; return function displayName() { console.log(name); }; } let peter = person(); peter(); // prints "Peter"

После выполнения функции person , движок JavaScript создаёт новый контекст исполнения и лексическое окружение для функции. После её завершения, мы возвращаем функцию displayName и присваиваем её к переменной peter .

PersonLexicalEnvironment = { environmentRecord: { name: "Peter", displayName: < displayName function reference> } outer: }

Когда функция person завершена, её контекст исполнения удаляется из стека. Но её лексическое окружение остаётся в памяти, потому что на него ссылается лексическое окружение внутренней функции displayName . Поэтому её переменные всё ещё доступны в памяти.

Когда функция peter выполнена (она является отсылкой к функции displayName), движок создаёт новый контекст исполнения и лексическое окружение для этой функции.

Её лексическое окружение выглядит так:

DisplayNameLexicalEnvironment = { environmentRecord: { } outer:

Так как в функции displayName нет переменных, её запись о внешних условиях будет пустой. В процессе выполнения этой функции, движок JavaScript попытается найти переменную name , в её лексическом окружении.

Так как в лексическом окружении функции displayName нет переменных, JS будет смотреть во внешнем окружении, а именно, в лексическом окружении функции person , которая всё ещё в памяти. Движок JavaScript найдёт переменную и выведет name в консоли.

Пример №2

Function getCounter() { let counter = 0; return function() { return counter++; } } let count = getCounter(); console.log(count()); // 0 console.log(count()); // 1 console.log(count()); // 2

И снова лексическое окружение. Для функции getCounter оно выглядит так:

GetCounterLexicalEnvironment = { environmentRecord: { counter: 0, : < reference to function> } outer: }

Она возвращает анонимную функцию и присваивает её переменной count .

После выполнения функции count , её лексическое окружение выглядит так:

CountLexicalEnvironment = { environmentRecord: { } outer: }

Когда вызвана функция count , движок JavaScript будет искать переменную counter в лексическом окружении этой функции. И снова, запись окружения пуста, поэтому движок будет смотреть во внешнем лексическом окружении функции.

Движок найдёт переменную, выведет её в консоли и инкрементирует переменную счётчик в лексическом окружении функции getCounter .

После первого вызова функции count , лексическое окружение для функции getCounter будет выглядеть так:

GetCounterLexicalEnvironment = { environmentRecord: { counter: 1, : < reference to function> } outer: }

При каждом вызове функции count , движок JavaScript создаёт для неё новое лексическое окружение, инкрементирует переменную counter и обновляет лексическое окружение функции getCounter , чтобы отразить изменения.

Заключение

Теперь вы знаете, что такое замыкания и как они работают. Замыкания - это базовая концепция JavaScript, которую должен понимать каждый JS разработчик. Эти знания помогут вам быть более эффективным в разработке.

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment ). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

Lexical scoping

Consider the following:

Function init() { var name = "Mozilla"; // name is a local variable created by init function displayName() { // displayName() is the inner function, a closure alert(name); // use variable declared in the parent function } displayName(); } init();

init() creates a local variable called name and a function called displayName() . The displayName() function is an inner function that is defined inside init() and is only available within the body of the init() function. Note that the displayName() function has no local variables of its own. However, since inner functions have access to the variables of outer functions, displayName() can access the variable name declared in the parent function, init() .

Var counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } }; })(); console.log(counter.value()); // logs 0 counter.increment(); counter.increment(); console.log(counter.value()); // logs 2 counter.decrement(); console.log(counter.value()); // logs 1

In previous examples, each closure has had its own lexical environment. Here, though, we create a single lexical environment that is shared by three functions: counter.increment , counter.decrement , and counter.value .

The shared lexical environment is created in the body of an anonymous function, which is executed as soon as it has been defined. The lexical environment contains two private items: a variable called privateCounter and a function called changeBy . Neither of these private items can be accessed directly from outside the anonymous function. Instead, they must be accessed by the three public functions that are returned from the anonymous wrapper.

Those three public functions are closures that share the same environment. Thanks to JavaScript"s lexical scoping, they each have access to the privateCounter variable and changeBy function.

You"ll notice we"re defining an anonymous function that creates a counter, and then we call it immediately and assign the result to the counter variable. We could store this function in a separate variable makeCounter and use it to create several counters.

Var makeCounter = function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } }; var counter1 = makeCounter(); var counter2 = makeCounter(); alert(counter1.value()); /* Alerts 0 */ counter1.increment(); counter1.increment(); alert(counter1.value()); /* Alerts 2 */ counter1.decrement(); alert(counter1.value()); /* Alerts 1 */ alert(counter2.value()); /* Alerts 0 */

Notice how each of the two counters, counter1 and counter2 , maintains its independence from the other. Each closure references a different version of the privateCounter variable through its own closure. Each time one of the counters is called, its lexical environment changes by changing the value of this variable; however changes to the variable value in one closure do not affect the value in the other closure.

Using closures in this way provides a number of benefits that are normally associated with object-oriented programming -- in particular, data hiding and encapsulation.

Closure Scope Chain

For every closure we have three scopes:-

  • Local Scope (Own scope)
  • Outer Functions Scope
  • Global Scope

So, we have access to all three scopes for a closure but often make a common mistake when we have nested inner functions. Consider the following example:

// global scope var e = 10; function sum(a){ return function(b){ return function(c){ // outer functions scope return function(d){ // local scope return a + b + c + d + e; } } } } console.log(sum(1)(2)(3)(4)); // log 20 // We can also write without anonymous functions: // global scope var e = 10; function sum(a){ return function sum2(b){ return function sum3(c){ // outer functions scope return function sum4(d){ // local scope return a + b + c + d + e; } } } } var s = sum(1); var s1 = s(2); var s2 = s1(3); var s3 = s2(4); console.log(s3) //log 20

So, in the example above, we have a series of nested functions all of which have access to the outer functions" scope, but which mistakenly guess only for their immediate outer function scope. In this context, we can say all closures have access to all outer function scopes within which they were declared.

Creating closures in loops: A common mistake function showHelp(help) { document.getElementById("help").innerHTML = help; } function setupHelp() { var helpText = [ {"id": "email", "help": "Your e-mail address"}, {"id": "name", "help": "Your full name"}, {"id": "age", "help": "Your age (you must be over 16)"} ]; helpText.forEach(function(text) { document.getElementById(text.id).onfocus = function() { showHelp(text.help); } }); } setupHelp(); Performance considerations

It is unwise to unnecessarily create functions within other functions if closures are not needed for a particular task, as it will negatively affect script performance both in terms of processing speed and memory consumption.

For instance, when creating a new object/class, methods should normally be associated to the object"s prototype rather than defined into the object constructor. The reason is that whenever the constructor is called, the methods would get reassigned (that is, for every object creation).

Consider the following case:

Function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); this.getName = function() { return this.name; }; this.getMessage = function() { return this.message; }; }

Because the previous code does not take advantage of the benefits of using closures in this particular instance, we could instead rewrite it to avoid using closure as follows:

Function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype = { getName: function() { return this.name; }, getMessage: function() { return this.message; } };

However, redefining the prototype is not recommended. The following example instead appends to the existing prototype:

Function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype.getName = function() { return this.name; }; MyObject.prototype.getMessage = function() { return this.message; };

In the two previous examples, the inherited prototype can be shared by all objects and the method definitions need not occur at every object creation. See

Всем привет! В этой статье мы рассмотрим, что такое замыкание в javascript .

Это довольно простая тема, но она требует понимания. Для начала давайте рассмотрим, что происходит внутри функции.

Function greeting(name) {
// LexicalEnvironment = {name: "Николай", text: undefined}
var text = "Здравствуйте, " + name;
// LexicalEnvironment = {name: "Николай", text: "Здравствуйте, Николай"}
alert(text);
}

Greeting("Николай");

Что здесь происходит и что такое LexicalEnvironment ? Давайте разберемся.

Когда функция вызывается, у нее создается объект LexicalEnvironment , в который записываются все локальные переменные и функции, а также ссылка на внешнюю область видимости(об этом позже). В нашем случае у нас есть локальная переменная name , у которой сразу есть значение(то, которое мы передаем) и это "Николай". В одной из статей я уже писал, однако напомню, что интерпретатор все знает про все переменные заранее. Именно по этому у нас в самом начале функции уже есть переменная text , интерпретатор знает про нее, но так как мы еще не дошли по присваивания этой переменной какого-то значения, то она равна undefined . Теперь мы присваиваем переменной значение, и наш объект LexicalEnvironment меняется. Его свойство text становится равным тому, что мы записали("Здравствуйте, Николай" в нашем случае). После того, как функция отработала, объект LexicalEnvironment уничтожается. При последующих вызовах функции он будет создан снова и т.д.

Теперь перейдем к следующему примеру. Скажите, что будет выведено в этом случае?

Var b = 2;
function x(a) {
alert(a + b);
}
x(1);

Подумали? Думаю, большинство ответило, что будет выведено число 3, и это правильный ответ, однако можете вы рассказать, как интерпретатор узнал о переменной b ? Ведь ее нет в теле функции. Если нет, давайте разбираться.

На самом деле в javascript есть скрытое свойство, которое называется [] . Когда функция объявляется, то она всегда объявляется где-то. Эта функция может быть в другой функции, может быть в глобальном объекте и т.д. В нашем случае функция объявлена в глобальном объекте window , поэтому свойство x.[] = window .

Var b = 2;
function x(a) { // x.[] = window
// LexicalEnvironment = {a: 1} -> window
alert(a + b);
}
x(1);

Эта стрелочка у объекта LexicalEnvironment - это ссылка на внешнюю область видимости, и эта ссылка устанавливается по свойству [] . Таким образом в объекте LexicalEnvironment у нас будет ссылка на внешний объект window . Когда интерпретатор ищет переменную, то он сначала ищет ее в объекте LexicalEnvironment , затем, если он не нашел переменную, то он смотрим в ссылку, переходит во внешнюю область видимости и ищет ее там и так до конца. Если он нигде этой переменной не нашел, то будет ошибка. В нашем случае переменную a интерпретатор возьмет из объекта LexicalEnvironment , а переменную b из объекта window . Конечно, если у нас будет локальная переменная b с каким-то значением, то она запишется в объект LexicalEnvironment и впоследствии будет взята оттуда, а не из внешней области видимости.

ВАЖНО! Запомните, что свойство [] устанавливается по тому месту, где функция была объявлена, а не вызвана, именно поэтому код ниже выведет число 3, а не 5, как некоторые могли подумать.

Bar b = 2;
function x(a) {
alert(a + b);
}

Function y() {
var b = 4;
x(1);
}

Это все была прелюдия только для того, чтобы вы поняли, как это все работает, и вам было легче понять, как работают замыкания. А теперь перейдем непосредственно к теме статьи.

Как я уже говорил, объект LexicalEnvironment уничтожается каждый раз после выполнения функции и создается снова при повторном вызове. Однако что, если мы хотим сохранить эти данные? Т.е. мы хотим, чтобы все, что записано в LexicalEnvironment сейчас, сохранилось и было использовано при следующих вызовах? Именно для этого и существуют замыкания .

Function greeting(name) {
// LexicalEnvironment = {name: "Николай"}
return function() { // [] = LexicalEnvironment
alert(name);
};
}

Var func = greeting("Николай");
greeting = null;
func();

Давайте посмотрим, что мы сделали. Сначала мы создаем функцию greeting , в которую передается имя. В функции создается объект LexicalEnvironment , где создается свойство(наша локальная переменная) name и ей присваивается имя "Николай". А теперь важно: мы возвращаем из функции другую функцию, внутри которой выводим через alert переменную name . Дальше мы присваиваем переменной func значение, возвращенное из функции greeting , а это значение - наша функция, которая выводит имя. Теперь мы greeting присваиваем null , т.е. мы просто уничтожаем нашу функцию greeting , однако, когда мы вызовем func , то увидим значение переменной name ("Николай") функции greeting . Как такое возможно, скажете вы? А очень просто. Все дело в том, что наша возвращаемая функция также имеет свойство [] , которое ссылается на внешнюю область видимости, а эта внешняя область видимости в нашем случае - объект LexicalEnvironment нашей функции greeting . Поэтому, несмотря на то, что мы удалили нашу функцию greeting , объект LexicalEnvironment не удалился и остался в памяти, и он будет оставаться в памяти до тех пор, пока на него будет хотя бы одна ссылка. У нас эта ссылка - наша возвращаемая функция, которая использует переменную name этого объекта LexicalEnvironment .

Итак, давайте теперь дадим определение тому, что такое замыкание .

Замыкание - функция вместе со всеми переменными, которые ей доступны.

Что же, статья получилась довольно объемная, но это только потому, что я попытался как можно подробнее описать весь процесс работы замыкания. На закрепление хочу привести простой пример - счетчик с использованием только что изученной темы. Пожалуйста, разберитесь с кодом и напишите в комментариях, как и почему он работает. Если вы чего-то не поняли, вы также можете задать вопрос. Спасибо за внимание!

Function makeCounter() {
var currentCount = 0;

Return function() {
currentCount++;
return currentCount;
};
}

Var counter = makeCounter();
counter();
counter();
alert(counter()); // 3