Замыкания и функции IIFE
Замыкания
Замыкание (closure) представляют собой конструкцию, когда функция, созданная в одной области видимости, запоминает свое лексическое окружение даже в том случае, когда она выполняет вне своей области видимости. Замыкание технически включает три компонента:
- внешняя функция, которая определяет некоторую область видимости и в которой определены некоторые переменные - лексическое окружение
- переменные (лексическое окружение), которые определены во внешней функции
- вложенная функция, которая использует эти переменные
// внешняя функция
function outer(){
// некоторая переменная
var n;
// вложенная функция
return inner(){
// действия с переменной n
}
}
Рассмотрим замыкания на простейшем примере:
function outer(){
let x = 5;
function inner(){
x++;
console.log(x);
};
return inner;
}
// fn = inner, так как функция outer возвращает функцию inner
let fn = outer();
// 6
fn();
// 7
fn();
// 8
fn();
Здесь функция outer
задает область видимости, в которой определены внутренняя функция inner
и переменная x
. Переменная x
представляет лексическое окружение для функции inner
. В самой функции inner
инкрементируем переменную x
и выводим ее значение на консоль. В конце функция outer
возвращает функцию inner
.
Далее вызываем функцию outer:
let fn = outer();
Поскольку функция outer
возвращает функцию inner
, то переменная fn
будет хранить ссылку на функцию inner
. При этом эта функция запомнила свое окружение - то есть внешнюю переменную x
.
Далее мы фактически три раза вызываем функцию inner
, и мы видим, что переменная x, которая определена вне функции inner
, инкрементируется:
// 6
fn();
// 7
fn();
// 8
fn();
То есть несмотря на то, что переменная x
определена вне функции inner
, эта функция запомнила свое окружение и может его использовать, несомотря на то, что она вызывается вне функции outer
, в которой была определена. В этом и суть замыканий.
Рассмотрим еще один пример:
function multiply(n){
var x = n;
return function(m){ return x * m;};
}
var fn1 = multiply(5);
var result1 = fn1(6); // 30
console.log(result1); // 30
var fn2= multiply(4);
// 24
var result2 = fn2(6);
// 24
console.log(result2);
Итак, здесь вызов функции multiply()
приводит к вызову другой внутренней функции. Внутренняя же функция:
function(m){ return x * m;};
Запоминает окружение, в котором она была создана, в частности, значение переменной x
.
В итоге при вызове функции multiply
определяется переменная fn1
, которая и представляет собой замыкание, то есть объединяет две вещи: функцию и окружение, в котором функция была создана. Окружение состоит из любой локальной переменной, которая была в области действия функции multiply
во время создания замыкания.
То есть fn1
— это замыкание, которое содержит и внутреннюю функцию function(m){ return x * m;}
, и переменную x
, которая существовала во время создания замыкания.
При создании двух замыканий: fn1
и fn2
, для каждого из этих замыканий создается свое окружение.
При этом важно не запутаться в параметрах. При определении замыкания:
var fn1 = multiply(5);
Число 5
передается для параметра n
функции multiply
.
При вызове внутренней функции:
var result1 = fn1(6);
Число 6
передается для параметра m во внутреннюю функцию function(m){ return x * m;};
.
Также мы можем использовать другой вариант для вызова замыкания:
function multiply(n){
var x = n;
return function(m){ return x * m;};
}
// 30
var result = multiply(5)(6);
console.log(result);
Самовызывающиеся функции
Обычно определение функции отделяется от ее вызова: сначала мы определяем функцию, а потом вызываем. Но это необязательно. Мы также можем создать такие функции, которые будут вызываться сразу при определении. Такие функции еще называют Immediately Invoked Function Expression (IIFE)
. Подобные функции заключаются в скобки, и после определения функции идет в скобках передача параметров:
(function(){
console.log("Привет мир");
}());
(function (n){
var result = 1;
for(var i=1; i<=n; i++)
result *=i;
console.log("Факториал числа " + n + " равен " + result);
}(4));