Замыкания позволяют программистам на JavaScript писать лучший код

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

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

Это относительно короткий (и приятный) пост о деталях замыканий в JavaScript. Вы должны быть знакомы с областью видимости переменных JavaScript, прежде чем читать дальше, потому что для понимания замыканий вы должны понимать область видимости переменных JavaScript.

Что такое закрытие?

Замыкание - это внутренняя функция, которая имеет доступ к переменным внешней (включающей) функции - цепочке областей видимости.

Замыкание имеет три цепочки областей видимости:

  • Он имеет доступ к своей собственной области видимости (переменные, определенные в фигурных скобках).
  • Он имеет доступ к переменным внешней функции.
  • У него есть доступ к глобальным переменным.

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

Вы создаете замыкание, добавляя функцию внутри другой функции. Замыкания широко используются в Node.js; они - рабочие лошадки в асинхронной неблокирующей архитектуре Node.js.

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

Базовый пример замыканий в JavaScript:

function showName (firstName, lastName) {
var nameIntro = "Your name is ";
// this inner function has access to the outer function's     //variables, including the parameter
function makeFullName () {     
return nameIntro + firstName + " " + lastName;   
}
return makeFullName ();
}
showName ("Michael", "Jackson"); // Your name is Michael Jackson

Правила закрытия и побочные эффекты

Замыкания имеют доступ к переменной внешней функции даже после возврата из внешней функции

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

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

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

Этот пример демонстрирует:

function celebrityName (firstName) {
    var nameIntro = "This celebrity is ";
// this inner function has access to the outer function's               //variables, including the parameter
   function lastName (theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return lastName;
}

var mjName = celebrityName ("Michael"); // At this juncture, the  //celebrityName outer function has returned.

// The closure (lastName) is called hereafter the outer function //has returned above
// Yet, the closure still has access to the outer function's //variables and parameter
mjName ("Jackson"); // This celebrity is Michael Jackson

Замыкания хранят ссылки на переменные внешней функции

Они не хранят фактическую стоимость.

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

function celebrityID () {
var celebrityID = 999;
//We are returning an object with some inner functions
//All the inner functions have access to the outer function's //variables
    return {
        getID: function ()  {
//This inner function will return the UPDATED celebrityID variable
//It will return the current value of celebrityID, even after the //changeTheID function changes it
          return celebrityID;
        },
        setID: function (theNewID)  {
//This inner function willchange outer function's variable anytime
            celebrityID = theNewID;
        }
    }
}

var mjID = celebrityID (); // At this juncture, the celebrityID //outer function has returned.
mjID.getID(); // 999
mjID.setID(567); // Changes the outer function's variable
mjID.getID(); // 567: It returns the updated celebrityId variable

Неудачное закрытие

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

Таким образом:

// This example is explained in detail below (just after this code box).
function celebrityIDCreator (theCelebrities) {
    var i;
    var uniqueID = 100;
    for (i = 0; i < theCelebrities.length; i++) {
      theCelebrities[i]["id"] = function ()  {
        return uniqueID + i;
      }
    }
    return theCelebrities;
}

var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];

var createIdForActionCelebs = celebrityIDCreator (actionCelebs);

var stalloneID = createIdForActionCelebs[0]console.log(stalloneID.id()); // 103

В предыдущем примере к моменту вызова анонимных функций значение i равно 3 (длина массива, а затем она увеличивается).

Число 3 было добавлено к uniqueID, чтобы создать 103 для всех celebritiesID. Таким образом, каждая позиция в возвращаемом массиве получает id = 103 вместо предполагаемых 100, 101, 102.

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

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

Чтобы исправить этот побочный эффект (ошибку) при закрытии, вы можете использовать выражение немедленно вызываемой функции (IIFE), например следующее:

function celebrityIDCreator (theCelebrities) {
    var i;
    var uniqueID = 100;
    for (i = 0; i < theCelebrities.length; i++) {
        theCelebrities[i]["id"] = function (j)  { 
//the j parametric variable is the i passed in on invocation of this //IIFE
            return function () {
                return uniqueID + j; // each iteration of the for //loop passes the current value of i into this IIFE and it saves the //correct value to the array
            } () // BY adding () at the end of this function, we are //executing it immediately and returning just the value of //uniqueID+ j, instead of returning a function.
        } (i); // immediately invoke the function passing the i //variable as a parameter
    }

    return theCelebrities;
}

var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];

var createIdForActionCelebs = celebrityIDCreator (actionCelebs);

var stalloneID = createIdForActionCelebs [0];
console.log(stalloneID.id); // 100

var cruiseID = createIdForActionCelebs [1];
console.log(cruiseID.id); // 101