Замыкания

Последнее обновление: 26.03.2024

Замыкание (closure) представляют собой конструкцию, когда функция, созданная в одной области видимости, запоминает свое лексическое окружение даже в том случае, когда она выполняет вне своей области видимости.

Для организации замыкания необходимы три компонента:

  • внешняя функция, которая определяет некоторую область видимости и в которой определены некоторые переменные - лексическое окружение

  • переменные (лексическое окружение), которые определены во внешней функции

  • вложенная функция, которая использует эти переменные

function outer(){		// внешняя функция
	let n;				// некоторая переменная
	return inner(){		// вложенная функция
		// действия с переменной n
	}
}

Грубо говоря, функция inner и представляет в данном случае замыкание, которое запонимание свое лексического окружение - переменную х.

Рассмотрим замыкания на простейшем примере:

function outer(){
    let x = 5;
    function inner(){
		x++;
		console.log(x);
	};
	return inner;
}
const fn = outer();	// fn = inner, так как функция outer возвращает функцию inner
// вызываем внутреннюю функцию inner
fn();	// 6
fn();	// 7
fn();	// 8

Здесь функция outer задает область видимости, в которой определены внутренняя функция inner и переменная x. Переменная x представляет лексическое окружение для функции inner. В самой функции inner инкрементируем переменную x и выводим ее значение на консоль. В конце функция outer возвращает функцию inner.

Далее вызываем функцию outer:

const fn = outer();

Поскольку функция outer возвращает функцию inner, то константа fn будет хранить ссылку на функцию inner. При этом эта функция запомнила свое окружение - то есть внешнюю переменную x.

Далее мы фактически три раза вызываем функцию Inner, и мы видим, что переменная x, которая определена вне функции inner, инкрементируется:

fn();	// 6
fn();	// 7
fn();	// 8

То есть несмотря на то, что переменная x определена вне функции inner, эта функция запомнила свое окружение и может его использовать, несомотря на то, что она вызывается вне функции outer, в которой была определена. В этом и суть замыканий.

Причем для каждой копии замыкания определяется своя копия лексического окружения:

// определяем объект-пространство имен
function outer(){
    let x = 5;
    function inner(){
		x++;
		console.log(x);
	};
	return inner;
}
const fn1 = outer();
const fn2 = outer();
fn1();	// 6
fn1();	// 7
fn2();	// 6
fn2();	// 7

Здесь видно, что для fn1 и fn2 есть своя копия переменной х, которой они манипулируют независимо друг от друга.

Рассмотрим еще один пример:

function multiply(n){
	let x = n;
	return function(m){ return x * m;};
}
const fn1 = multiply(5);
const result1 = fn1(6); // 30
console.log(result1); // 30

const fn2= multiply(4);
const result2 = fn2(6); // 24
console.log(result2); // 24

Итак, здесь вызов функции multiply() приводит к вызову другой внутренней функции. Внутренняя же функция:

function(m){ return x * m;};

запоминает окружение, в котором она была создана, в частности, значение переменной x.

В итоге при вызове функции multiply определяется константа fn1, которая и представляет собой замыкание, то есть объединяет две вещи: функцию и окружение, в котором функция была создана. Окружение состоит из любой локальной переменной, которая была в области действия функции multiply во время создания замыкания.

То есть fn1 — это замыкание, которое содержит и внутреннюю функцию function(m){ return x * m;}, и переменную x, которая существовала во время создания замыкания.

При создании двух замыканий: fn1 и fn2, для каждого из этих замыканий создается свое окружение.

При этом важно не запутаться в параметрах. При определении замыкания:

const fn1 = multiply(5);

Число 5 передается для параметра n функции multiply.

При вызове внутренней функции:

const result1 = fn1(6);

Число 6 передается для параметра m во внутреннюю функцию function(m){ return x * m;};.

Также мы можем использовать другой вариант для вызова замыкания:

function multiply(n){
	let x = n;
	return function(m){ return x * m;};
}
const result = multiply(5)(6); // 30
console.log(result);

Замыкания и объектно-ориентированное программирование

Хотя объектно-ориентированное программирование будет рассматриваться далее, тем не менее нельзя не заметить, что замыкания по сути явились предтечей объектно-ориентированного программирования. И использование замыканий в некоторой степени позволяет имитировать создание объектов и работу с ними. Например, рассмотрим следующий код:

function person(name, age){
    console.log("Person", name, "created");

    function print(){
         console.log("Person ", name, " (" +age +")");
    }
    function work(){
         console.log("Person ", name, " works");
    }
    function incrementAge(value){
        age = age + value;
    }
    return [print, work, incrementAge];
}

const tom = person("Tom", 39);
tom[0]();       // print
tom[1]();       // work
tom[2](1);      // incrementAge
tom[0]();       // print

Функция person принимает два параметра и определяет три вложенных функции. По сути эти вложенных функции и образуют замыкания, а в качестве лексического окружения используют параметры функции person.

Здесь функция person выступает своего рода конструктором объекта person - условного человека, а ее параметры name и age принимают извне соответственно имя и возраст человека. Три вложенных функции - print, work, incrementAge могут обращаться к своему лексическому окружению - параметрам функции person.

Чтобы к этим функциям можно было обратиться извне, функция person возвращает вложенные функции в виде массива:

return [print, work, incrementAge];

Далее мы можем вызвать функцию person, передав в нее некоторые параметры, и получить ее результат - условного человека:

const tom = person("Tom", 39);

Что такое в данном случае константа tom? По сути это возвращенный массив из трех функций, котоорые позволяют манипулировать человеком tom. Соответственно, обратившись к определенному элементу в массиве, мы можем вызвать соответствующую функцию:

tom[0]();       // print

То есть выражение tom[0] возвращает функцию print, а выражение tom[0]() вызывает ее. В итоге в данном случае вывод консоли браузера будет следующим:

Person Tom created
Person  Tom  (39)
Person  Tom  works
Person  Tom  (40)
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850