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

Замыкания в javascript используются для того, чтобы скрывать значения переменных и хранить значения функций. Суть в том, что при замыкании создается одна функция, в которой задаются переменные и которая в результате свое работы возвращает свою вложенную функцию. Затем в ней (в основной функции) создается вложенная функция, в которой делаются какие-то операции с переменными основной функции и которая возвращает результат этих операций. Далее основная функция приравнивается к какой-то переменной - эта переменная может вызываться сколько угодно раз и при этом в ней будут храниться и обновляться значения переменных основной функции т.к. она «замкнута».

Как известно, в JavaScript областью видимости локальных переменных (объявляемых словом var) является тело функции, внутри которой они определены.

Если вы объявляете функцию внутри другой функции, первая получает доступ к переменным и аргументам последней:

Код: function outerFn(myArg) {
var myVar;
function innerFn() {
//имеет доступ к myVar и myArg
}
}

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

Рассмотрим пример - функцию, возвращающую кол-во собственных вызовов:

Код: function createCounter() {
var numberOfCalls = 0;
return function() {
return ++numberOfCalls;
}
}
var fn = createCounter();
fn(); //1
fn(); //2
fn(); //3

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

Именно за эти свойства такие «вложенные» функции в JavaScript называют замыканиями (термином, пришедшим из функциональных языков программирования) - они «замыкают» на себя переменные и аргументы функции, внутри которой определены.

Применение замыканий

Упростим немножко пример выше - уберем необходимость отдельно вызывать функцию createCounter, сделав ее аномимной и вызвав сразу же после ее объявления:

Код: var fn = (function() {
var numberOfCalls = 0;
return function() {
return ++ numberOfCalls;
}
})();

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

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

Код: var createHelloFunction = function(name) {
return function() {
alert("Hello, " + name);
}
}
var sayHelloHabrahabr = createHelloFunction("Habrahabr");
sayHelloHabrahabr(); //alerts «Hello, Habrahabr»

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

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

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

Код: Function.prototype.bind = function(context) {
var fn = this;
return function() {
return fn.apply(context, arguments);
};
}
var HelloPage = {
name: "Habrahabr",
init: function() {
alert("Hello, " + this.name);
}
}
//window.onload = HelloPage.init; //алертнул бы undefined, т.к. this указывало бы на window
window.onload = HelloPage.init.bind(HelloPage); //вот теперь всё работает

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

Следующее, принципиально иное применение замыканий - защита данных (инкапсуляция) . Рассмотрим следующую конструкцию:

Код: (function() {

})();

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

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

Код: (function() {
//вышестоящее this сохранится
}).call(this);

Рассмотрим еще один прием из той же серии. Повсеместно популяризовали его разработчики фреймворка Yahoo UI, назвав его «Module Pattern» и написав о нем целую статью в официальном блоге.
Пускай у нас есть объект (синглтон), содержащий какие-либо методы и свойства:

Код: var MyModule = {
name: "Habrahabr",
sayPreved: function(name) {
alert("PREVED " + name.toUpperCase())
},
this.sayPreved(this.name);
}
}
MyModule.sayPrevedToHabrahabr();

С помощью замыкания мы можем сделать методы и свойства, которые вне объекта не используются, приватными (т.е. доступными только ему) :

Код: var MyModule = (function() {
var name = "Habrahabr";
function sayPreved() {
alert("PREVED " + name.toUpperCase());
}
return {
sayPrevedToHabrahabr: function() {
sayPreved(name);
}
}
})();
MyModule.sayPrevedToHabrahabr(); //alerts «PREVED Habrahabr»

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

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

Код: for (var i = 0; i < links.length; i++) {
alert(i);
}
}

На деле же оказывается, что при клике на любую ссылку выводится одно и то же число - значение links.length. Почему так происходит? В связи с замыканием объявленная вспомогательная переменная i продолжает существовать, при чем и в тот момент, когда мы кликаем по ссылке. Поскольку к тому времени цикл уже прошел, i остается равным кол-ву ссылок - это значение мы и видим при кликах.

Решается эта проблема следующим образом:

Код: for (var i = 0; i < links.length; i++) {
(function(i) {
links[i].onclick = function() {
alert(i);
}
})(i);
}

Здесь с помощью еще одного замыкания мы «затеняем» переменную i, создавая ее копию в его локальной области видимости на каждом шаге цикла. Благодаря этому все теперь работает как задумывалось.

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

зы
Для сохранений между вызовами проще использовать func_name.attr типа:

Код: function countIt(reset) {
if (reset ||! countIt.cnt) countIt.cnt = 0;
return countIt.cnt++;

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

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

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

Простое описание

Если говорить просто, то замыкания - это внутренние функции. Ведь javascript разрешает создавать функции по ходу выполнения скрипта. И эти функции имеют доступ к переменным внешней функции.

В этом примере создается внутренняя функция func , изнутри которой доступны как локальные переменные, так и переменные внешней функции outer :

function outer() { var outerVar; var func = function () { var innerVar ... x = innerVar + outerVar } return func }

Когда заканчивает работать функция outer , внутренняя функция func остается жить, ее можно запускать в другом месте кода.

Получается, что при запуске func используется переменная уже отработавшей функции outer , т.е самим фактом своего существования, func замыкает на себя переменные внешней функции (а точнее - всех внешних функций).

Наиболее часто замыкания применяются для назначения функций-обработчиков событий:

Здесь динамически созданный обработчик события handler использует targetId из внешней функции для доступа к элементу.

Если Вы хотите углубиться поглубже и разбираться подольше..

На самом деле происходящее в интерпретаторе Javascript гораздо сложнее и содержит куда больше деталей, чем здесь описано...

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

[]

Каждое выполнение функции хранит все переменные в специальном объекте с кодовым именем [], который нельзя получить в явном виде, но он есть .

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

Такова внутренняя структура "области видимости" - обычный объект. Все изменения локальных переменных являются изменениями свойств этого неявного объекта.

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

Общий поток выполнения выглядит так:

Кстати, для кода вне функции(и вообще глобальных переменных) роль объекта-контейнера [] выполняет объект window .

Область видимости вложенной функции

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

Именно за счет этого из внутренней функции можно получить переменные внешней функции - через ссылку на ее [] . Сначала ищем у себя, затем - во внешнем [] - и так далее по цепочке до самого объекта window .

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

Внутренняя функция может обратиться к нему в любой момент и получить переменную внешней функции.

Например, разберем работу функции, которая устанавливает обработчики событий:

function addHideHandler(sourceId, targetId) { // создан объект [] со свойствами sourceId, targetId // записать в [] свойство sourceNode var sourceNode = document.getElementById (sourceId) // записать в [] свойство handler var handler = function () { var targetNode = document.getElementById (targetId) targetNode.style .display = ‘none’ } sourceNode.onclick = handler // функция закончила выполнение // (***) и тут - самое интересное! }

При запуске функции все происходит стандартно:

  1. создается []
  2. туда записываются локальные переменные
  3. внутренняя функция получает ссылку на []

Но в самом конце - внутренняя функция присваивается sourceNode.onclick . Внешняя функция закончила свою работу, но внутренняя - может запуститься когда-нибудь потом.

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

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

Чтобы когда внутренняя функция запустится, если она вдруг не найдет какую-либо переменную в своем [] - она могла обратиться к [] внешней функции и нашла бы ее там.

Если внешняя функция была создана внутри еще одной (еще более внешней) функции - то в цепочку добавляется еще один консервированный [] и так - до глобальной области window .

Пример на понимание

В этом примере внешняя функция makeShout () создает внутреннюю shout ().

function makeShout() { var phrase = "Превед!" var shout = function () { alert (phrase) } phrase = "Готово!" return shout } shout = makeShout() // что выдаст? shout()

Функция shout () на правах внутренней функции имеет доступ к переменной phrase . Какое значение она выведет - первое или второе?

А вот - подробное описание происходящего в недрах javascript:

  1. Внутри makeShout ()
    1. создается []
    2. В [] пишется: phrase="Превед!"
    3. В [] пишется: shout=..функция..
    4. shout получает ссылку на [] внешней функции
    5. [].phrase меняется на новое значение "Готово!"
  2. При запуске shout()
    1. Создается свой собственный объект []
    2. Ищется phrase в [] - не найден
    3. Ищется phrase в [] внешней функции - найдено значение "Готово!"
    4. alert("Готово!")

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

Пример ошибочного использования

Функция addEvents принимает массив div "ов и ставит каждому вывод своего номера на onclick .

С вопроса "Почему это не работает?" люди обычно начинают изучение замыканий.

function addEvents(divs) { for (var i=0 ; i

Для тестового примера сделаем 10 разноцветных нумерованных div "ов с разными цветами:

function makeDivs(parentId) { for (var i=0 ;i<10 ;i++) { var j = 9 -i var div = document.createElement ("div" ) div.style .backgroundColor = "#" +i+i+j+j+j+i div.className ="closure-div" div.style .color = "#" +j+j+i+i+i+j document.getElementById (parentId) .appendChild (div) } }

Кнопка ниже создаст 10 дивов и вызовет для них addEvents

Если Вы покликаете на div"ы - они все выдают одинаковый alert.

Такой глюк возник из-за того, что все функции div[i].onclick получают значение i из одного на всех [] внешней функции. А это значение ([].i ) на момент активации onclick -обработчика равно 10 (цикл завершился как только i==10 ).

Чтобы все было в порядке, в таких случаях применяют специальный прием - выделение [] . Следующая функция работает правильно. В ней все то же самое, кроме div.onclick .

function addEvents2(divs) { for (var i=0 ; i

Теперь все должно быть в порядке - каждый div дает alert на свой номер.

Для присваивания div.onclick запускается временная функция function(x) {..} , принимающая аргумент x и возвращающая обработчик, который берет x из [] этой временной функции.

Function(x) {..} используется для создания функции, и тут же (i) - для запуска с аргументом i .

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

alert ( function a() { return [ 5 ] } () [ 0 ] ) // => выведет 5

Временная функция function(x) {..} заканчивает работать тут же, оставляя в своем [] правильное значение x , равное текущей переменной i цикла.

Когда обработчик активизируется - alert возьмет из [] ближайшей внешней функциии правильное значение x .

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

В 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 объектах.

Последнее обновление: 30.03.2018

Замыкания

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

Замыкание технически включает три компонента:

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

    переменные (лексическое окружение), которые определены во внешней функции

    вложенная функция, которая использует эти переменные

function outer(){ // внешняя функция var n; // некоторая переменная return inner(){ // вложенная функция // действия с переменной n } }

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

Function outer(){ let x = 5; function inner(){ x++; console.log(x); }; return inner; } let fn = outer(); // fn = inner, так как функция outer возвращает функцию inner // вызываем внутреннюю функцию inner fn(); // 6 fn(); // 7 fn(); // 8

Здесь функция outer задает область видимости, в которой определены внутренняя функция inner и переменная x. Переменная x представляет лексическое окружение для функции inner. В самой функции inner инкрементируем переменную x и выводим ее значение на консоль. В конце функция outer возвращает функцию inner.

Let fn = outer();

Поскольку функция outer возвращает функцию inner, то переменная fn будет хранить ссылку на функцию inner. При этом эта функция запомнила свое окружение - то есть внешнюю переменную x.

Fn(); // 6 fn(); // 7 fn(); // 8

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

Рассмотрим еще один пример:

Function multiply(n){ var x = n; return function(m){ return x * m;}; } var fn1 = multiply(5); var result1 = fn1(6); // 30 console.log(result1); // 30 var fn2= multiply(4); var result2 = fn2(6); // 24 console.log(result2); // 24

Итак, здесь вызов функции multiply() приводит к вызову другой внутренней функции. Внутренняя же функция:

Function(m){ return x * m;};

запоминает окружение, в котором она была создана, в частности, значение переменной x.

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

То есть fn1 - это замыкание, которое содержит и внутреннюю функцию function(m){ return x * m;} , и переменную x, которая существовала во время создания замыкания.

При создании двух замыканий: fn1 и fn2 , для каждого из этих замыканий создается свое окружение.

При этом важно не запутаться в параметрах. При определении замыкания:

Var fn1 = multiply(5);

Число 5 передается для параметра n функции multiply.

При вызове внутренней функции:

Var result1 = fn1(6);

Число 6 передается для параметра m во внутреннюю функцию function(m){ return x * m;}; .

Также мы можем использовать другой вариант для вызова замыкания:

Function multiply(n){ var x = n; return function(m){ return x * m;}; } var result = multiply(5)(6); // 30 console.log(result);

Самовызывающиеся функции

Обычно определение функции отделяется от ее вызова: сначала мы определяем функцию, а потом вызываем. Но это необязательно. Мы также можем создать такие функции, которые будут вызываться сразу при определении. Такие функции еще называют Immediately Invoked Function Expression (IIFE).

(function(){ console.log("Привет мир"); }()); (function (n){ var result = 1; for(var i=1; i<=n; i++) result *=i; console.log("Факториал числа " + n + " равен " + result); }(4));

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