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