Основы

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

Скажу честно: когда я впервые попытался узнать о замыканиях, я совершенно не понимал, что пытались сказать объяснения, которые я читал. Многие, кажется, пронизаны нечеткими ссылками на местоимения и страдают от чрезмерного употребления слова «функция». Только намного позже я понял, что использую их в течение нескольких месяцев; Закрытие просто казалось логическим продолжением того, что я уже делал, и я не придал этому ни единой мысли, тем более что было известно, что у того, что я делал, было особое название.

Дайвинг в

Замыкания - это на самом деле невероятно простая концепция. Если у вас уже есть базовое представление о том, как работает область видимости JavaScript, это будет совсем несложно. В противном случае это будет немного сложнее, но все же не слишком.

Мне нравится использовать слово «вложение» при объяснении замыканий, потому что в некотором смысле замыкание существует в вложении (его родительская функция) и является само вложение: содержит свои собственные переменные и, возможно, собственные замыкания, все внутри него невидимо для остальной части вашего приложения.

Замыкание JavaScript - это очень просто любая функция, которая существует внутри другой функции. Он выглядит, как любая другая функция, и не требует специальных действий, чтобы «превратить его в» замыкание, за исключением того, что он должен существовать внутри другой функции. Он может быть объявлен с помощью function, или const, или, если вы испытываете ностальгию, с помощью var. (Если хотите, он работает даже с new Function.)

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

Ниже приведен неуниверсальный пример замыкания, в котором находятся все простые числа от 2 до 17; само закрытие выделено полужирным шрифтом.

Обратите внимание: чтобы все было как можно проще, чтобы новые кодировщики (или программисты, пришедшие с таких языков, как Python или Ruby) могли легко следовать, я реализую самый простой (и самый медленный) решатель простых чисел, который я знаю, объявляя числа 1– 17 в виде массива и с использованием цикла while с синтаксисом, подобным Python / Ruby. Эта реализация никоим образом не рекомендуется :)

function primesUpToSeventeen() {
  const numbers = [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17];
  const primes  = [];
  function isPrime(number) { //this is our closure.
    if (number < 2) 
      return false;
    else if (number === 2) 
      return true;
    let divisor = 2;
    while (divisor < number) {
      if (number%divisor === 0) 
         return false;
      else
        divisor += 1
    };
    return true
  };
  numbers.forEach(number=> {
    if (isPrime(number)) //isPrime() closure is being called here
      primes.push(number)
  });
  return primes
};
//returns [2, 3, 5, 7, 11, 13, 17]

В приведенном выше примере isPrime() - это замыкание, размещенное внутри своей родительской функции, primesUpToSeventeen(). Родитель не знает и не заботится о том, что происходит внутри isPrime(); он ничего не знает о своих внутренних переменных, о том, какие функции (замыкания!) он может содержать (в данном случае у него их нет), и даже если внутри isPrime() объявлены переменные, имена которых совпадают с именами переменных в собственном primesUpToSeventeen() сфера. Все, что он знает, это то, что isPrime() сообщает ему, когда он завершает свою работу; в этом случае isPrime() вернет либо true, либо false. Это все, что действительно знает его родительская функция. *

Итак, пока все хорошо; мы куда-то идем. Но приведенная выше функция на самом деле является довольно бесполезным примером. Мы можем переместить isPrime() из primesUpToSeventeen() следующим образом:

function primesUpToSeventeen() {
  //primesUpToSeventeen() without isPrime() code here
}
function isPrime(number) {
  //isPrime code here
}

… И он будет вести себя идентично.

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

function isPrime(number) { //exists in global scope; not a closure
  //...code here
};
function plusSixPrimesUpToSeventeen() { //our outer function
  const numbers = [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17];
  const primes  = [];
  numbers.forEach(number=> { //add primes we find to primes array
    if (isPrime(number))
      primes.push(number)
  });
  function findPlusSixPrimes() { //this is our closure
    const plusSixPrimes = [];
    primes.forEach(prime=> {
      const primePlusSix = prime + 6
      if (isPrime(primePlusSix)) {
        plusSixPrimes.push(prime)
      };
    });
    return plusSixPrimes
  };
  return findPlusSixPrimes() //we call our closure here
};
//returns [5, 7, 11, 13, 17] because 11, 13, 17, 19 and 23 are prime

Обратите внимание, что мы не передали в закрытие никаких переменных. Нам не пришлось; isPrime() существует в глобальной области видимости, поэтому наше замыкание может использовать его, когда захочет, а массив primes, который мы заполнили ранее в верхней части нашей основной («внешней») функции (plusSixPrimesUpToSeventeen()), существует в том же пространстве (области), что и наше замыкание. .

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

* Обратите внимание, что это неверно для переназначения; объявление переменной внутри замыкания, даже переменной с именем, уже используемым вне замыкания, не приведет к каким-либо проблемам с пространством имен; однако переназначение или изменение переменной (например, numbers = [] или numbers.length = 0) приведет к изменению этой внешней переменной. В этом конкретном случае номера все равно нельзя переназначить, поскольку это константа, и даже если бы это было не так, я использовал цикл forEach, поэтому переназначение numbers на самом деле не повлияет на вывод функции. Но важно помнить, что замыкания абсолютно могут изменить любую переменную, к которой у него есть доступ (что хорошо!).