MyTetra Share
Делитесь знаниями!
Замыкания в JavaScript - хорошее объяснение
Время создания: 10.05.2023 14:13
Текстовые метки: язык, JavaScript, замыкание
Раздел: Компьютер - Программирование - Java Script
Запись: xintrea/mytetra_syncro/master/base/1683717182a1x1rx0al1/text.html на raw.github.com

Замыкания в JavaScript ― явление воистину великолепное и прекрасное. Но перед тем, как читать эту статью, надо освежить в памяти, как работают области видимости и лексическое окружение в JavaScript.

Самое простое определение, которое можно найти в интернете, действительно "очень простое и легко запоминается":


Замыкание — это комбинация функции и лексического окружения, в котором эта функция была определена. Другими словами, замыкание даёт вам доступ к Scope внешней функции из внутренней функции. В JavaScript замыкания создаются каждый раз при создании функции, во время её создания.


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

Прекрасно само по себе название механизма. Замыкание ― слово, по сути, уже содержащее в себе всё, что нужно для понимания.

Итак, мы помним, что для функции лексическое окружение создаётся в момент её вызова, чтобы собрать все актуальные данные. Если функция была вызвана внутри другой, то она получает ссылку на внешнее лексическое окружение, которым будет являться как раз ЛО внешней функции. Но что, если функция задекларирована (создана) внутри другой функции? Нужна картинка, пусть код будет нам уже слегка знаком по главе о лексическом окружении.

Сперва напомню тот код, что мы уже видели:


let outer = 'Hello';


function sayHello(text) {

console.log(`${outer}, ${text}!` )

}


function sayHelloDearFriend(name) {

let inner = 'dear friend'

sayHello(`${inner}, ${name}`)

}


sayHello('Mike');

sayHelloDearFriend('Chester');


А теперь давайте его преобразим, чтобы он больше подходил под ситуацию.


function sayHello(name) {

let hello = 'Hello'


function say() {

console.log(`${hello}, ${name}!` )

}


say()

}


sayHello('Chester');


В JavaScript замыканием является любая функция. Для иллюстрации возможностей данного функционала и большей наглядности предлагаю рассматривать частный случай. Необходимое нам замыкание было создано, когда и функция say() внутри функции sayHello. Пункту про лексическое окружение это никак не противоречит. Внутри функции say нет никаких переменных, поэтому переменные hello и name будем искать через ссылку на внешнее окружение, которым как раз является ЛО функции sayHello(). Оттуда мы забираем нужные переменные, и ничего интересного не происходит.

Пойдём дальше.

Сделаем так, чтобы функция sayHello не вызывала внутри себя функцию say, а возвращала её в результате выполнения:


function sayHello(name) {

let hello = "Hello";


function say() {

console.log(`${hello}, ${name}!`);

}


return say;

}


let sayHelloChester = sayHello("Chester");


sayHelloChester();

sayHelloChester();

sayHelloChester();


То есть мы вернули результат выполнения sayHello(‘Chester’) в переменную sayHelloChester. И хотя функция sayHello уже отработала, по идее, её лексическое окружение должно быть удалено сборщиком мусора, так как мы помним, что ЛО создаётся заново каждый раз при вызове функции, а лексическое окружение отработавшей функции удаляется за ненадобностью. Сейчас мы видим абсолютно другую картину. Сколько бы раз мы не вызвали теперь  sayHelloChester() у нас будет доступ внутри неё и к hello, и к name, который вообще параметром передавался.

Почему? Все просто. Определив функцию say внутри функции sayHello, мы замкнули (в прямом смысле замкнули, никаких переносных значений) запись окружения функции sayHello на неё. И теперь, пока существует sayHelloChester, который является по факту функцией say, со знанием того лексического окружения, в котором её создали, мы запретили сборщику мусора удалять это ЛО. Он не может удалить переменную, пока на неё хоть кто-то ссылается.

С большой силой приходит большая ответственность. Теперь мы понимаем, что пока существует sayHelloChester, он будет хранить в памяти информацию, необходимую для корректной работы. Да, в нашем примере её не то чтобы много, но в реальных может быть достаточно. А потому такие вещи, как sayHelloChester, нужно принудительно уничтожать, если в них больше нет необходимости.

Бонус. На собеседованиях с лайвкодингом есть банальная задача на замыкания, которую часто можно встретить. Она обычно называется «Функция счетчик». Нужно написать функцию, которая при каждом её вызове будет выводить в консоль количество раз, которые её вызвали, при этом нельзя создавать никакие переменные вне этой функции, также нельзя использовать объект window для хранения промежуточных значений.

Вот, собственно, так она выглядит:


let counter = (function(){

let counter = 0

return function() {

console.log(++counter)

}

})()


counter();

counter();

counter();

counter();


Вывод в консоль:


> 1

> 2

> 3

> 4


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

Таким образом, замыкания ― это механизм, который позволяет функциям запоминать лексическое окружение, в котором они были созданы. С его помощью в JavaScript можно делать следующие вещи: инкапсуляция (сокрытие какого-либо функционала и данных из глобальной области видимости) и состояние (у функции, которая находится в переменной counter, есть своё состояние, которое модифицируется при каждом вызове счетчика).


Так же в этом разделе:
 
MyTetra Share v.0.59
Яндекс индекс цитирования