Замыкание (closure) представляет объект функции, который запоминает свое лексическое окружение даже в том случае, когда она выполняется вне своей области видимости.
Технически замыкание включает три компонента:
внешняя функция, которая определяет некоторую область видимости и в которой определены некоторые переменные и параметры - лексическое окружение
переменные и параметры (лексическое окружение), которые определены во внешней функции
вложенная функция, которая использует переменные и параметры внешней функции
Function outer(){ // внешняя функция var n; // некоторая переменная - лексическое окружение Function inner(){ // вложенная функция // действия с переменной n } return inner; }
Рассмотрим замыкания на простейшем примере:
Function outer(){ // внешняя функция var n = 5; // некоторая переменная - лексическое окружение функции inner void inner(){ // вложенная функция // действия с переменной n n++; print(n); } return inner; } void main() { Function fn = outer(); // fn = inner, так как функция outer возвращает функцию inner // вызываем внутреннюю функцию inner fn(); // 6 fn(); // 7 fn(); // 8 }
Здесь функция outer задает область видимости, в которой определены внутренняя функция inner и переменная n. Переменная n представляет лексическое окружение для функции inner. В самой функции inner инкрементируем переменную n и выводим ее значение на консоль. В конце функция outer возвращает функцию inner.
Далее вызываем функцию outer:
Function fn = outer();
Поскольку функция outer возвращает функцию inner, то переменная fn будет хранить ссылку на функцию inner. При этом эта функция запомнила свое окружение - то есть внешнюю переменную n.
Далее мы фактически три раза вызываем функцию inner, и мы видим, что переменная n, которая определена вне функции inner, увеличивается на единицу:
fn(); // 6 fn(); // 7 fn(); // 8
То есть несмотря на то, что переменная n определена вне функции inner, эта функция запомнила свое окружение и может его использовать, несомотря на то, что она вызывается вне функции outer, в которой была определена. В этом и суть замыканий.
Кстати мы можем сократить определение функции outer, используя анонимную функцию:
Function outer(){ var n = 5; return (){ n++; print(n); }; }
Рассмотрим другой пример:
Function multiply(int n){ return (int m) => n * m; } void main() { Function func = multiply(5); int result1 = func(6); // 30 print(result1); // 30 int result2 = func(5); // 25 print(result2); // 25 }
Итак, здесь вызов функции multiply()
приводит к вызову другой внутренней функции. Внутренняя же функция:
(int m) => n * m;
запоминает окружение, в котором она была создана, в частности, значение параметра n.
В итоге при вызове функции multiply определяется переменная func
, которая и представляет собой замыкание, то есть объединяет две вещи:
функцию и окружение, в котором функция была создана. Окружение состоит из любой локальной переменной или любого параметра, которые были определены в области действия функции multiply во время создания замыкания.
То есть result1 — это замыкание, которое содержит и внутреннюю функцию (int m) => n * m;
, и параметр n, который существовал во время создания замыкания.
При этом важно не запутаться в параметрах. При определении замыкания:
Function func = multiply(5);
Число 5 передается для параметра n функции multiply.
При вызове внутренней функции:
int result1 = func(6); // 30
Число 6 передается для параметра m во внутреннюю функцию (int m) => n * m;
Также мы можем использовать другой вариант для вызова замыкания:
Function multiply(int n){ return (int m) => n * m;; } void main() { int result1 = multiply(5)(6); // 30 print(result1); // 30 }