Я думаю, что это простой способ понять, что такое закрытие JavaScript.

Шаг 1. Переменные Объявление

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

const foo = 0;
const foo = 1;  // This raise `SyntaxError`.
var bar = 0;
var bar = 1;  // This overwrites the previous one.

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

Итак, мы можем использовать Scope. Область действия — это область, в которой действуют объявления переменных.

Шаг 2. Область действия

Сначала давайте взглянем на пример масштаба.

let foo = "outer foo";
let bar = "outer bar";
{
    let foo = "inner foo";
    console.log(foo);  // "inner foo"
    console.log(bar);  // "outer bar"
    foo = "inner assignment of foo"
    bar = "inner assignment of bar"
}
console.log(foo);  // "outer foo"
console.log(bar);  // "inner asignment of bar"

Говоря о переменных, объявленных let или const, фигурные скобки создают области видимости.

Если мы объявляем переменную в фигурной скобке, мы можем объявить ее, даже если мы уже объявили ее за пределами скобки.

Когда мы используем переменную, если она была объявлена ​​в области видимости, это та переменная, которая объявлена ​​в области видимости. И только когда она не была объявлена ​​в области видимости, переменная является той, которая объявлена ​​вне скобки. Это следует правилу:

Ищите переменную в области видимости, а если ее нет, ищите внешнюю.

Мы также можем присвоить им значения.

Примечание: var и фигурные скобки

Объявление var отличается от let и const с точки зрения объема.

Область действия переменных с объявлением var находится только внутри функции, а фигурные скобки игнорируются.

var foo = "outer";
{
    var foo = "inner curly bracket"; 
    // This overwrite the variable declaration.
    const func = function(){
        var foo = "inner function";
    };
}
console.log(foo);  // "inner curly bracket"

Объявление функции ведет себя так же. Объявление функции и var взяты из ES5, а let и const — из функции ES6.

Шаг 3. Объем функций

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

Взгляните на этот пример:

const func = function (){
    const foo = 0;
    console.log(foo);
};
func();  // 0
func();  // 0 :Here occurs no error.

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

Далее, что вы думаете о случае, если объявление выходит за пределы функции, как показано ниже? :

const foo = "assignment over the DECLARATION";
const func = function (){
    console.log(foo);
};
func();  // "assignment over the DECLARATION"
{
    const foo = "assignment over the EXECUTION";
    func();  // Which?
}

Этот код следует правилу, которое мы уже видели:

Ищите переменную в области видимости, а если ее нет, ищите внешнюю.

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

  1. «внешний» означает место, которое функция объявила.
  2. «внешний» означает место, в котором выполняется функция.

Первый шаблон называется статическая область или лексическая область, а второй — динамическая область.

JavaScript принимает лексическую область видимости, поэтому вывод кода равен "assignment over the DECLARATION".

Шаг 4. Закрытие

Так как насчет аналогичного случая ниже?:

let foo = "the first assignment";
const func = function (){
    console.log(foo);
};
foo = "the second assignment";
func();  // Which?

Вы так думаете? : «Когда объявлена ​​функция, значение foo равно "the first assignment". Таким образом, результат равен "the first assignment"».

Это не верно. Этот код говорит "the second assignment". Функции хранят переменные из внешней области видимости как переменную как есть, а не как копию. Таким образом, если значение переменной изменяется, результат также изменяется, даже если изменение производится после объявления функции.

Таким образом, конечно, это значение можно изменить изнутри функции, и эффект распространяется на переменную снаружи.

let count = 0;
const countUp = function (){
    count++;
    console.log(count);
};
countUp();  // 1
console.log(count);  // 1

Эти функции, которые удерживают переменные из внешней области видимости: лексические переменные, называются замыканиями.

Примечание. Время жизни переменных в функции

Эта заметка предназначена для тех, кто заботится о памяти или сборке мусора.

Обычно после завершения выполнения значение, объявленное в функции, выбрасывается из памяти.

const func = function(){
    let count = 0;
    console.log(count);
};
func();  // 0
// Here, the `count` used in `func` have already disappeared from memory.

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

const func = function(){
    let count = 0;
    const innerFunc = function (){
        count++;
        console.log(count);
    };
    return innerFunc;
};
const outerFunc = func();
outerFunc();  // 1
outerFunc();  // 2

Шаг 5. Типичные схемы закрытия

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

Поэтому я думаю, что знание типичных и классифицированных шаблонов использования замыканий — это хороший способ познакомиться с замыканиями. Хотя есть и другие варианты использования замыканий, я покажу 5 основных паттернов. Они просты, но при небольшом воображении мы можем найти в них множество практических применений.

Наличие состояния

Как мы видели ранее в countUp, мы можем сохранять состояние внутри замыканий.

let count = 0;
const doOnlyOnce = function (){
    if(count==0){
        console.log("You do this for first time.");
    }else{
        console.log("You already have done this.");
    }
    count++;
};
doOnlyOnce();  // "You do this for first time."
doOnlyOnce();  // "You already have done this."
doOnlyOnce();  // "You already have done this."

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

Этот пример также можно улучшить, создав замыкание из функции, потому что оно не загрязняет внешнюю область видимости и может использоваться повторно, как показано ниже:

const createFunc = function(){
    let count = 0;
    const doOnlyOnce = function (){
        if(count==0){
            console.log("You do this for first time.");
        }else{
            console.log("You already have done this.");
        }
        count++;
    };
    return doOnlyOnce;
};
const myDoOnlyOnce = createFunc();
const anotherDoOnlyOnce = createFunc();
myDoOnlyOnce();  // "You do this for first time."
myDoOnlyOnce();  // "You already have done this."
anotherDoOnlyOnce();  // "You do this for first time."

Все приведенные ниже примеры возвращают функцию закрытия из функции.

Функции, разделяющие состояние с другими функциями

Важно знать, что мы можем создавать некоторые замыкания, которые используют одни и те же переменные.

const createFuncs = function(){
    let innerVariable = 0;
    const countUp = function(value){
        innerVariable++;
    };
    const show = function(){
        console.log(innerVariable);
    };
    return [countUp, show];
};
const [myCountUp, myShow] = createFuncs();
const [anotherCountUp, anotherShow] = createFuncs();
myCountUp();
myShow();  // 1
anotherShow();  // 0

Это innerVariable является значением, которое действует как частная переменная, часто используемая в объектно-ориентированном программировании.

И мы можем изменить пример, больше похожий на ООП, как показано ниже.

const construct = function(){
    let innerVariable = 0;
    const countUp = function(value){
        innerVariable++;
    };
    const show = function(){
        console.log(innerVariable);
    };
    return {countup, show};
};
const myObj = construct();
myObj.countUp();
myObj.show();  // 1

Получить функцию с уже установленным параметром

Когда мы пишем код, мы часто создаем функции с некоторыми аргументами, и некоторые из них почти всегда одинаковы, когда мы их используем. Затем мы можем создавать новые функции, которым уже задано значение аргументов в качестве замыкания.

// This is a example of normal function.
const exponential = function(base, exponent){
    return base ** exponent;
}
console.log(exponential(8, 2));  // 64
// We may want to use it with always base being 2.
// So we can do it like this.
const powerOf = function(exponent){
    const pow = function(base){
        return base ** exponent;
    };
    return pow;
};
const square = powerOf(2);
const cube = powerOf(3);
console.log(square(8));  // 64
console.log(square(16));  // 256
console.log(cube(4));  // 64

Сделайте что-нибудь с предварительно обработанным значением

Иногда мы хотим использовать аргументы всегда после того, как мы применили к ним некоторый предварительный процесс.

Замыкания позволяют разделить препроцесс и основной алгоритм. Тогда становится легко изменить предварительный процесс.

const greeting = function(preprocessFunc){
    const output = function(name){
        const nameDecolated = preprocessFunc(name);
        console.log(nameDecolated);
    };
    return output;
};
const preprocess1 = function(name){
    return "Hello, "+ name + "!";
};
const preprocess2 = function(name){
    return "Good morning, "+ name + ":)";
};
const hello = greeting(preprocess1);
const morning = greeting(preprocess2);
hello("Yusuke");  // Hello, Yusuke!
morning("Yusuke");  // Good morning, Yusuke :)

Выполнить что-либо до и/или после выполнения

Этот паттерн типичен, как и пример, для измерения того, сколько времени занимает выполнение.

const withTimeMesuring = function(funcToExecute){
    const func = function(arg){
        console.time(‘timer’);
        console.log("***start of execution***");
        funcToExecute(arg);
        console.log("***end of execution***");
        console.timeEnd(‘timer’);
    };
    return func;
};
const incrementManyTimes = function(times){
    count = 0;
    for(let i=0;i<times;i++){
        count++;
    }
    console.log("Done!");
};
const myFunc = withTimeMesuring(incrementManyTimes);
myFunc(1000000);
// ***start of execution***
// Done!
// ***end of execution***
// timer: 4.391845703125ms