Флешка

Замыкания в JavaScript: практический пример, особенности и правила. JavaScript - Что такое замыкание? Как работают замыкания javascript

Замыкания в JavaScript: практический пример, особенности и правила. 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 // функция закончила выполнение // (***) и тут - самое интересное! }

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

  • создается []
  • туда записываются локальные переменные
  • внутренняя функция получает ссылку на []
  • Но в самом конце - внутренняя функция присваивается 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 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

    Последнее обновление: 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

    Copyright © 2024. Компьютерный журнал