Реальные примеры использования замыкания 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

Замыкание - одно из мощных выразительных средств 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 // функция закончила выполнение // (***) и тут - самое интересное! }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Как известно, в 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++;