Не понимаю зачем нужны замыкания в js. Что такое замыкание в javascript

Замыкание - это комбинация функции и лексического окружения, в котором эта функция была определена.

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

Рассмотрим следующий пример:

Function init() { var name = "Mozilla"; // name - локальная переменная, созданная в init function displayName() { // displayName() - внутренняя функция, замыкание alert (name); // displayName() использует переменную, объявленную в родительской функции } displayName(); } init();

init() создаёт локальную переменную name и определяет функцию displayName() . displayName() - это внутренняя функция - она определена внутри init() и доступна только внутри тела функции init() . Обратите внимание, что функция displayName() не имеет никаких собственных локальных переменных. Однако, поскольку внутренние функции имеют доступ к переменным внешних функций, displayName() может иметь доступ к переменной name , объявленной в родительской функции 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; } }; })(); alert(Counter.value()); /* Alerts 0 */ Counter.increment(); Counter.increment(); alert(Counter.value()); /* Alerts 2 */ Counter.decrement(); alert(Counter.value()); /* Alerts 1 */

Тут много чего поменялось. В предыдущем примере каждое замыкание имело свой собственный контекст исполнения (окружение). Здесь мы создаем единое окружение для трех функций: Counter.increment , Counter.decrement , и Counter.value .

Единое окружение создается в теле анонимной функции, которая исполняется в момент описания. Это окружение содержит два приватных элемента: переменную privateCounter и фукцию changeBy(val) . Ни один из этих элементов не доступен напрямую, за пределами этой самой анонимной функции. Вместо этого они могут и должны использоваться тремя публичными функциями, которые возвращаются анонимным блоком кода (anonymous wrapper), выполняемым в той же анонимной функции.

Эти три публичные функции являются замыканиями, использующими общий контекст исполнения (окружение). Благодаря механизму lexical scoping в Javascript, все они имеют доступ к переменной privateCounter и функции changeBy .

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

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 */

Заметьте, что счетчики работают независимо друг от друга. Это происходит потому, что у каждого из них в момент создания функцией makeCounter() также создавался свой отдельный контекст исполнения (окружение). То есть приватная переменная privateCounter в каждом из счетчиков это действительно отдельная, самостоятельная переменная.

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

Создание замыканий в цикле: Очень частая ошибка

До того, как в версии ECMAScript 6 ввели ключевое слово let , постоянно возникала следующая проблема при создании замыканий внутри цикла. Рассмотрим пример:

Helpful notes will appear here

E-mail:

Name:

Age:

function showHelp(help) { document.getElementById("help").innerHTML = help; } function setupHelp() { var helpText = [ {"id": "email", "help": "Ваш адрес e-mail"}, {"id": "name", "help": "Ваше полное имя"}, {"id": "age", "help": "Ваш возраст (Вам должно быть больше 16)"} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } } } setupHelp();

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

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

Проблема в том, что функции, присвоенные как обработчики события onfocus , являются замыканиями. Они состоят из описания функции и контекста исполнения (окружения), унаследованного от функции setupHelp . Было создано три замыкания, но все они были созданы с одним и тем же контекстом исполнения. К моменту возникновения события onfocus цикл уже давно отработал, а значит, переменная item (одна и та же для всех трех замыканий) указывает на последний элемент массива, который как раз в поле возраста.

В качестве решения в этом случае можно предложить использование функции, фабричной функции (function factory), как уже было описано выше в примерах:

Function showHelp(help) { document.getElementById("help").innerHTML = help; } function makeHelpCallback(help) { return function() { showHelp(help); }; } function setupHelp() { var helpText = [ {"id": "email", "help": "Ваш адрес e-mail"}, {"id": "name", "help": "Ваше полное имя"}, {"id": "age", "help": "Ваш возраст (Вам должно быть больше 16)"} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = makeHelpCallback(item.help); } } setupHelp();

Вот это работает как следует. Вместо того, чтобы делить на всех одно окружение, функция makeHelpCallback создает каждому из замыканий свое собственное, в котором переменная item указывает на правильный элемент массива helpText .

Соображения по производительности

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

Как пример, при написании нового класса есть смысл помещать все методы в прототип его объекта, а не описывать их в тексте конструктора. Если сделать по-другому, то при каждом создании объекта для него будет создан свой экземпляр каждого из методов, вместо того, чтобы наследовать их из прототипа.

Давайте рассмотрим не очень практичный, но показательный пример:

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

Поскольку вышеприведенный код никак не использует преимущества замыканий, его можно переписать следующим образом:

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

Методы вынесены в прототип. Тем не менее, переопределять прототип - само по себе является плохой привычкой, поэтому давайте перепишем всё так, чтобы новые методы просто добавились к уже существующему прототипу.

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; };

Код выше можно сделать аккуратнее:

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

Всем привет! В этой статье мы рассмотрим, что такое замыкание в 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

Это практическое руководство по работе с замыканиями в 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 разработчик. Эти знания помогут вам быть более эффективным в разработке.

В программировании замыкание или в англоязычной версии «закрытие» - это метод реализации контекстного имени связывания в языке функций первого класса. Оперативно она представляет собой запись, хранящую функцию вместе со средой. Окружающая среда представляет собой сопоставление каждой свободной функции со значением или ссылкой по имени, созданной замыканием в Javascript. Она позволяет доступ к захваченным переменным, через копии значений или ссылок, даже когда вызывается вне области.

Концепция замыканий

Закрытия были разработаны в 1960-х годах для механической оценки выражений в исчислении и применены в 1970 году как особенность языка программирования PAL для поддержки функций первого класса с лексической сферой. Питер Ландин дал определение термину "замыкание" в 1964 году со средой и контрольной частью, применяемых на машине SECD с целью оценки лямбда-выражений, связанных лексической средой, что приводило к закрытию их или замыканию в Javascript.

Такое объяснение вошло в 1975 году как лексически ограниченный вариант LISP и стало широко распространенным. Лексическая среда является множеством действительных переменных в программе. Она состоит из внутренней лексической среды и ссылок на внешнюю среду, называемую нелокальными переменными.

Лексические замыкания в Javascript являются функциями с ее внешней средой. Как и в JavaScript, все переменные имеют ссылку на тип. JS использует только привязку по ссылке - которая соответствует в C ++ 11, а время жизни нелокальных переменных, захваченных функцией, распространяется на время жизни функции.

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

В этом примере выражение lambda (lambda (book) (>= (book-sales book) threshold)) появляется внутри функции best-selling-books. Когда вычисляется лямбда-выражение, схема создает замыкание, состоящее из кода для выражения лямбда и ссылки на threshold переменную, которая является свободной переменной внутри выражения лямбда. Замыкание затем передается filter функции, которая вызывает ее неоднократно, чтобы определить, какие книги должны быть добавлены в список результатов и которые должны быть отброшены.

Поскольку тут замыкание в значении threshold, последняя может использовать ее каждый раз, когда ее filter вызывает. Сама функция filter может быть определена в совершенно отдельном файле. Вот тот же пример, переписанный в JS. Он демонстрирует, как работают замыкания под капотом в Javascript.

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

В языках без замыканий время жизни автоматической локальной переменной совпадает с исполнением фрейма стека, где объявлена эта переменная. В языках с Javascript замыкания и функции iife, переменные должны продолжать существовать до тех пор, пока любые существующие блокировки имеют ссылки на них. Это чаще всего реализуется с использованием некоторой формы сбора мусора.

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

Проблема с циклом возникает, когда пользователь создает функцию в цикле и ожидает, что текущее значение переменной останется в этой новой функции, даже если оно изменяется в контексте циклов перед вызовом новой функции. Замыкания, используемые таким образом, больше не имеют ссылочной прозрачности и, следовательно, больше не являются чистыми функциями, тем не менее, они обычно используются в нечистых функциональных языках, таких как Scheme. Для того чтобы понять, что такое замыкание в Javascript, нужно рассмотреть случаи их использования. На самом деле на практике они имеют много применений:

  1. Их можно использовать для определения структур управления. Например, все стандартные структуры управления Smalltalk, включая ветви (if / then / else) и циклы (while и for), определяются с использованием объектов, методы которых принимают замыкания. Пользователи также могут легко использовать замыкания для определения структуры управления. В языках, реализующих назначение, можно создавать ее многофункциональную среду, позволяя общаться конфиденциально и изменять эту среду. Замыкание используется для реализации объектных систем.
  2. Создание как частных, так и общедоступных методов переменных, используя шаблоны модуля. Из-за того, что возвращаемые функции наследуют область родительской функции, они доступны всем переменным и аргументам в данном контексте.
  3. Оно полезно в ситуации, когда функция использует один и тот же ресурс для каждого вызова, но и создает сам ресурс для него. Это обстоятельство делает метод неэффективным, которое устраняется исключительно замыканием.

Согласно MDN (Mozilla Developer Network) «Closures - это функции с независимыми переменными, которые «запоминают» среду своего создания». И, как правило, когда функция завершается, ее локальные переменные больше не существуют. Понять, как работают замыкание в Javascript, можно рассмотрев несколько механизмов. Первый - формальная логика. Например, применив функцию logName, которая принимает одно имя в качестве параметра и регистрирует его. Затем создаю цикл for, чтобы перебирать список имен, задавать 1-й тайм-аут, а затем вызывать функцию logName, проходящую в текущем имени.

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

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

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

  • var x = 3;
  • y = 5;
  • var z = x + y.

Или если не намерены повторно обработать номера:var z = 3 + 5;

Это и есть анонимные номера. Для анонимных функций можно объявить их, когда их используют «на лету» - без прохождения переменной. Например, взять функцию do из ранее:

{ alert("Ceci est une fonction anonyme.");

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

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

Это в действительности сложно, но в JavaScript есть способ отслеживать видимость переменных, и даже двумя способами. Назначение глобальной переменной в JavaScript имеют такой же механизм, как и в Java - сложные объекты, массивы, элементы DOM и другие передаются по ссылке, поэтому в следующем коде:

var tab = ; var tab2 = tab.

Где, tab и tab2 - две ссылки на одну и ту же таблицу, технически это указатели, управляемые сборщиком мусора. Функции также передаются по ссылке. Переменная globalFn больше не скрыта. Порядок позволяет это делать, что продемонстрировано на примере задачи на замыкание Javascript.

Вот как можно извлечь функцию из локального контекста, если функция удовлетворяет другим локальным переменным. Простой пример: auto-increment, функция, которая возвращает целое число, которое увеличивается на 1 при каждом вызове. Конкретно, нужна функция inc, которая ведет себя следующим образом:

// retourne 0 inc();

// retourne 1 inc();

// retourne 2 inc();

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

function makeInc() { var x = 0; return function() { return x++; } } var inc = makeInc();

В последней строке в тот момент, когда создается переменная функция inc, она несет в себе какие-то переменные, которые есть вокруг, в этом случае x. Он создает некий невидимый объект вокруг функции, который содержит эту переменную. Этот объект является функцией замыкания Javascript. При этом каждая копия функции будет иметь свое замыкание:

var inc1 = makeInc();

var inc2 = makeInc();

Как видно, замыкание очень полезно во многих случаях.

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

Естественно, A.x и B.x это не одна и та же переменная. Однако если просто нужно запустить скрипт, не требуя сохранения переменных для остальных, можно использовать анонимную функцию, как замыкание. Это дает несколько странный синтаксис. Хотя две строки кода в середине довольно обычны, с другой стороны, функция, которая находится вокруг, выполняется «на лету». Обращают внимание на круглые скобки ()в конце. И чтобы иметь возможность делать замыкание, анонимная функция сама должна быть окружена круглыми скобками.

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

Существует вариант: (function() {// ...}());

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

Javascript-программирование в циклах

Когда пользователь выполняет большие объемы Javascript-программирования, ему трудно избежать циклов. Кого-то это сводит с ума, после чего они приходят к мысли, что всякая реализация Javascript имеет серьезную ошибку. Если у разработчика уже есть цикл, который он не хочет преобразовывать, чтобы использовать функцию итератора, все, что ему нужно сделать, - это замыкание, в котором он определяет новые переменные. Они фиксируют текущее значение переменных, и изменяющихся на каждой итерации. Уловкой для захвата переменных является то, что внешнее замыкание выполняется сразу же во время текущей итерации цикла. Можно использовать один из этих двух примерных подходов

Теперь есть еще одно упрощенное решение этой проблемы, поскольку let ключевое слово поддерживается как в Firefox, так и в Chrome. Оно является ключевым слово вместо var переменного блока. Let работает магическим образом, потому что объявляется новую переменную j, значение i которой фиксируется замыканием внутри цикла. Однако надо учитывать, что оно не продолжает существовать после конца одной итерации цикла, поскольку оно локально.

Петля и функция

For Цикл в JavaScript не представляется, так же как for цикл в C или Java. На самом деле это больше похоже на PHP. Самое главное знание о циклах в JS заключается в том, что они не создают область действия. JS не имеет блок сферы, только функцию объема. Это свойство можно рассмотреть на следующем фрагменте:

function foo() {var bar = 1;

for(var i = 0; i< 42; i++) {var baz = i;} /* more code */}

Понятно, что bar доступно во всей функции. До первой итерации цикла baz будет иметь значение undefined. После цикла он будет иметь значение 41 (и i будет 42). Таким образом, всякая переменная, объявленная в любом месте функции, будет доступна везде в функции и будет иметь значение только после того, как она была назначена ему.

Затворы и агрегирование

Замыкание - это не что иное, как функции, внутри других функций, и передаются в какой-то другой контекст. Они называются замыканием, так как они закрывают через локальные переменные, то есть доступны к другим функциям сферы. Например, время, x определенное как параметр foo, и var bar = foo(2)() вернется 84.

Возвращаемая функция foo имеет доступ x. Это все важно, потому что помогает разработчикам создавать функции внутри циклов, зависящих от переменных цикла. Рассмотрим этот фрагмент, который присваивает click-обработчик различным элементам:

// elements is an array of 3 DOM elements var values = ["foo", "bar", "baz"];

i< l; i++) {var data = values[i];

elements[i].onclick = function() {alert(data);

Значение, которое они будут использовать alert при нажатии, будет одинаково для всех, а именно baz. К тому времени вызывается обработчик событий, for уже завершен. JS не имеет области блока, т.е. все обработчики используют ссылку на одну и ту же data переменную. После петли, это значение будет values. Каждое объявление переменной создает одно место в памяти хранения данных. В for эти данные снова и снова меняются, положение в памяти остается неизменным.

Каждый обработчик событий имеет доступ к одной и той же позиции в памяти. Единственное решение - ввести еще одну область, которая «фиксирует» текущее значение data. JS имеет только область функций. Поэтому вводится другая функция. Пример:

function createEventHandler(x) {return function() {alert(x);

for(var i = 0, l = elements.length;

i< l; i++) {var data = values[i];

elements[i].onclick = createEventHandler(data);

Это работает, потому что значение data будет храниться в локальной области, createEventHandler и эта функция выполняется на каждой итерации. Это можно записать короче, используя сразу исполняемые функции:

for(var i = 0, l = elements.length;

i< l; i++) {var data = values[i];

elements[i].onclick = (function(x) {function() {alert(x);

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

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

function work(name){

return function (topic) {

console.log(What is ${topic} in ${name});

work("Javascript")("Closure");

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

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

Пример внутренней функции

Подробнее о замыкании в Javascript можно рассказать на втором примере. Теперь эта среда исполнения уничтожается, но имя параметра все еще существует. Создается новая внутренняя функциональная среда, являющейся анонимной функцией. Она имеет доступ к области внешней лексической среды.

Таким образом, в переменной внешнего окружения все еще существует так, что анонимная функция имеющая доступ к переменной имени печатает в консоли, например, «Что такое замыкание в Javascript ». Внутренняя анонимная функция //main.js

function factory(){ var products = ;

i++){ products.push(function () { console.log(i);

} return products;

} var soap = factory();

Результат этого примера довольно незначителен и равен 2.

Когда мыло - soap () называется внешней переменной контекста, всегда 2, потому что в цикле условие ложно в i<2, поэтому при этом значение i равно 2, а во время вызова нужно напечатать значение в консоль так, она всегда пишет 2. То же самое для мыла - soap ().

Создание функций «на лету»

Можно создать фабрику функций - functionFactory, которая выполняет пользовательские задачи. Результирующая функция от фабрики функций будет замыканием, запоминающей среду создания.

var functionFactory = function(num1) {return function(num2) {return num1 * num2;

Вышеприведенное позволяет передать один номер functionFactory. Затем functionFactory возвращает Замыкание, запоминающее значение num1. Полученная функция умножает оригинальные num1 раз величина num2, который передается при вызове.

var mult5 = functionFactory(5);

var mult10 = functionFactory(10);

Вышеприведенное просто создает функции mult5 и mult10. Теперь можно ссылаться на любую из этих функций, передавая новый номер, который нужно умножить на 5 или 10. Теперь можно увидеть результат.

Замыкание - одна из самых мощных функций javascript, но она не может быть использована правильно без понимания сути. Их относительно легко создать случайно, вот чем опасны замыкания Javascript. Их создание имеет потенциально вредные последствия, особенно в некоторых относительно общих средах веб-браузера. Чтобы избежать случайного столкновения с недостатками и воспользоваться преимуществами, которые они предлагают, необходимо понять их механизм.

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

Введение

В сети довольно много статей, в которых пытаются объяснить области видимости и замыкания, но в общем, я бы сказал, что большинство из них не совсем понятны. Кроме того, в некоторых статьях предполагается, что вы программировали до этого на 15 других языках, хотя как я считаю - большинство людей пишущих на JavaScript имеют лишь опыт в HTML и CSS, а не в C или Java.

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

Область видимости

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

Глобальная область видимости

Когда что-то является глобальным, это значит, что оно доступно из любого места в вашем коде. Рассмотрим пример:

var monkey = "Gorilla"; function greetVisitor () { return alert("Hello dear blog reader!"); }

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

Локальная область видимости

В отличие от глобальной области видимости, локальная область видимости - это когда что-то определено и доступно только в некоторой части кода, как например функция. Рассмотрим пример:

function talkDirty () { var saying = "Oh, you little VB lover, you"; return alert(saying); } alert(saying); // Throws an error

В данном примере переменная saying доступна только внутри функции talkDirty, за пределами которой она не определена. Замечание: если бы вы определили saying без ключевого слова var, то она автоматически стала бы глобальной.

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

function saveName (firstName) { function capitalizeName () { return firstName.toUpperCase(); } var capitalized = capitalizeName(); return capitalized; } alert(saveName("Robert")); // Returns "ROBERT"

Как вы только что увидели, внутренней функции capitalizeName не нужно передавать никаких параметров, т.к. она имеет полный доступ к параметру firstName во внешней функции saveName. Для большей ясности, рассмотрим еще один пример:

function siblings () { var siblings = ["John", "Liza", "Peter"]; function siblingCount () { var siblingsLength = siblings.length; return siblingsLength; } function joinSiblingNames () { return "I have " + siblingCount() + " siblings:nn" + siblings.join("n"); } return joinSiblingNames(); } alert(siblings()); // Outputs "I have 3 siblings: John Liza Peter"

Как вы видите, обе внутренние функции имеют доступ к массиву siblings, и каждая внутренняя функция имеет доступ к другой внутренней функции того же уровня (в данном случае joinSiblingNames имеет доступ к siblingCount). Однако, переменная siblingsLength внутри siblingCount доступна лишь внутри этой функции, т.е. в этой области видимости.

Замыкание

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

function add (x) { return function (y) { return x + y; }; } var add5 = add(5); var no8 = add5(3); alert(no8); // Returns 8

Вот это да! Что здесь происходит? Давайте разбираться:

1. Когда мы вызываем функцию add, она возвращает функцию.

2. Эта функция закрывает контекст и запоминает, каким был параметр x в это время (т.е. в данном случае значением 5)

3. Когда результат функции add присваивается переменной add5, она всегда будет знать, каким был x при создании этой переменной.

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

5. Это означает, что когда мы вызываем add5 со значением 3, она сложит числа 5 и 3, и вернет 8.

На самом деле, в мире JavaScript, функция add5 выглядит следующим образом:

function add5 (y) { return 5 + y; }

Пресловутая проблема циклов
Сколько раз вы создавали циклы, в которых хотели присвоить значение i каким-либо образом, например элементу, и понимали, что возвращается лишь последнее значение i?

Неправильное обращение

Давайте посмотрим на этот некорректный код, который создает 5 элементов , добавляет значение i как текст к каждому элементу и onclick, который как ожидается будет выдавать alert со значением i для данной ссылки, т.е. то же самое значение, что и в тексте элемента. Затем элементы добавляются к document body:

<5; i++) { link = document.createElement("a"); link.innerHTML = "Link " + i; link.onclick = function () { alert(i); }; document.body.appendChild(link); } } window.onload = addLinks;

Каждый элемент содержит правильный текст, т.е. “Link 0″, “Link 1″ и т.д. Но какую бы ссылку вы не кликнули, она показывает alert с цифрой 5. В чем же дело? Причина в том, что значение переменной i увеличивается на 1 с каждой итерацией цикла, и т.к. событие onclick не исполняется, а просто применяется к элементу , то значение увеличивается.

Следовательно, цикл продолжает работу, пока i не станет равным 5, что является последним значением перед выходом из функции addLinks. Далее, всякий раз при срабатывании события onclick, берется последнее значение i.

Правильное обращение

Что вам нужно сделать, так это создать замыкание. В результате, когда вы будете применять значение i к событию onclick элемента , то будет присвоено значение i именно в тот момент времени. Например вот так:

function addLinks () { for (var i=0, link; i<5; i++) { link = document.createElement("a"); link.innerHTML = "Link " + i; link.onclick = function (num) { return function () { alert(num); }; }(i); document.body.appendChild(link); } } window.onload = addLinks;

Используя этот код, если вы кликните на первый элемент, alert выдаст "0", на второй - "1", и т.д. Решение состоит в том, что внутренняя функция, примененная к событию onclick, создает замыкание, в котором происходит обращение к параметру num, т.е. к значению i в тот момент времени.

Эта функция "запоминает" нужное значение, и может затем возвращать соответствующую цифру при срабатывании события onclick.

Безопасно-исполняющиеся функции

Безопасно-исполняющиеся функции - это такие функции, которые начинают исполняться сразу же и создают свое замыкание. Рассмотрим пример:

(function () { var dog = "German Shepherd"; alert(dog); })(); alert(dog); // возвращает undefined

Итак, переменная dog доступна только внутри данного контекста. Подумаешь, скрытая переменная dog... Но, друзья мои, с этого начинается самое интересное! Это решило нашу проблему с циклом, и это также является основой для Yahoo JavaScript Module Pattern.

Yahoo JavaScript Module Pattern

Суть этого паттерна состоит в том, что он использует безопасно-исполняющуюся функцию чтобы создать замыкание, следовательно это делает возможным использовать private и public свойства и методы. Простой пример:

var person = function () { // Private var name = "Robert"; return { getName: function () { return name; }, setName: function (newName) { name = newName; } }; }(); alert(person.name); // Undefined alert(person.getName()); // "Robert" person.setName("Robert Nyman"); alert(person.getName()); // "Robert Nyman"

Преимущество данного подхода в том, что вы можете определить сами, что будет открытым в вашем объекте (и может быть изменено), и что закрытым, к чему никто не сможет обратиться или изменить. Переменная name скрыта вне контекста функции, но доступна функциям getName и setName, т.к. они создают замыкания, в которых есть ссылка на переменную name.

Заключение

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

Удачного кодинга!