четверг, 19 ноября 2020 г.

A programmer: JavaScript #22. Closures (Замыкания)

Короткая и простая тема. Думал я поначалу. Есть, как минимум, два объяснения, что это такое.

Во-первых, замыкания - это, грубо говоря, способ создать приватное свойство. То есть есть свойство объявляется внутри функции, а сама функция его возвращает с помощию внутренней функции. Это означает, что свойство остается неизменным, кто бы ни вызывал функцию, потому что область его видимости остается внутри. Например, функция:

function private() {

    let a = 0;

    return function() {

        return a;

    };

};

В ней никто не имеет доступа к переменной а извне, и потому не может её изменить.

Во-вторых, замыкания - это функция внутри функции. Основная польза от этого в том, что её можно присвоить какой-то переменной, а вызвать потом через эту переменную. Например, результатом присвоения let variable = private(); будет variable, которая при вызове variable() будет возвращать а. Одним из самых распространенных применеий замыканий является счетчик: 

function private() {

    let a = 0;

    return function() {

        return a++;

    }

}

let counter = private(); 

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

Вот тут-то мне и стало непонятно: если функция отработала, то она должна была бы уничтожиться? Тогда в следующий раз заново бы создавалась и инициализировалась новая let a = 0;? Тогда о каком счетчике может быть речь?

Ответ пришлось поискать, и вот он: объект существует пока существует ссылка на него. И здесь мы имеем, что да, функция private() отработала, но внутренняя по отношению к ней функция продолжает существовать, поскольку в момент вызова private() её присвоили другому объекту (где она может ожидать своего вызова). А значит, и private() не уничтожена, а переменная изменила своё значение (в данном случае а = а+1)!

Если детализировать, то:

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

2. Также создании любая функции получает скрытое свойство [[Environment]], которое ссылается на её окружение (и тем самым как бы "запоминает" его). Для внутренней функции окружением будет внешняя функция с теми значениями переменных, которые на данный момент в ней есть.

То есть в момент вызова counter() значение переменной а будет находится во внешней по отношению к ней private(), и равняться это значение будет тому, которое существует на момент очередного вызова counter(). Другими словами, каждый новый counter() будет находить новую переменную а в своем окружении (имеющую то значение, которое осталось в результате предыдущего вызова counter()). Вот вам и счетчик.

Подробнее - здесь.

Комментариев нет: