Закрытие JavaScript против анонимных функций

Друг мой и я в настоящее время обсуждаем, что такое закрытие в JS, а что нет. Мы просто хотим убедиться, что мы действительно понимаем это правильно.

Возьмем этот пример. У нас есть цикл подсчета и вы хотите напечатать переменную счетчика на консоли. Поэтому мы используем setTimeout и замыкания, чтобы зафиксировать значение переменной счетчика, чтобы убедиться, что оно не будет печатать в N раз значение N.

Неправильное решение без закрытий или что-нибудь рядом с закрытием будет:

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

который, разумеется, напечатает в 10 раз значение i после цикла, а именно 10.

Таким образом, его попытка заключалась в следующем:

for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i;
        setTimeout(function(){
            console.log(i2);
        }, 1000)
    })();
}

печать от 0 до 9, как ожидалось.

Я сказал ему, что он не использует закрытие для захвата i, но он настаивает на том, что он есть. Я доказал, что он не использует закрытия, поместив тело for loop в другой setTimeout (передав свою анонимную функцию на setTimeout), снова напечатав 10 раз 10. То же самое происходит, если я сохраняю его функцию в var и выполняю ее после цикла, также печатаю 10 раз 10. Поэтому мой аргумент состоит в том, что он действительно не фиксирует значение i, делая его версию не закрытием.

Моя попытка:

for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2);
        }
    })(i), 1000);
}

Итак, я фиксирую i (названный i2 в закрытии), но теперь я возвращаю другую функцию и передаю ее. В моем случае функция, переданная в setTimeout, действительно захватывает i.

Теперь кто использует закрытие, а кто нет?

Обратите внимание, что оба решения печатают от 0 до 9 на консоли, поэтому они решают исходную проблему, но мы хотим понять, какое из этих двух решений использует закрытие для выполнения этого.

Ответ 1

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

Простое объяснение замыкания:

  • Возьмите функцию. Пусть это называется F.
  • Список всех переменных F.
  • Переменные могут быть двух типов:
    • Локальные переменные (связанные переменные)
    • Нелокальные переменные (свободные переменные)
  • Если F не имеет свободных переменных, то это не может быть замыканием.
  • Если F имеет любые свободные переменные (которые определены в a родительской области F), то:
    • Должна быть только одна родительская область F, к которой привязана свободная переменная a.
    • Если F является ссылкой из внешней , родительской области, то это становится закрытием для , свободной переменной.
    • Свободная переменная называется upvalue замыкания F.

Теперь позвольте использовать это, чтобы выяснить, кто использует закрытие, а кто нет (для объяснения я назвал функции):

Случай 1: программа вашего друга

for (var i = 0; i < 10; i++) {
    (function f() {
        var i2 = i;
        setTimeout(function g() {
            console.log(i2);
        }, 1000);
    })();
}

В вышеуказанной программе есть две функции: f и g. Посмотрим, закрыты ли они:

Для f:

  • Перечислите переменные:
    • i2 является локальной переменной.
    • i является переменной свободной.
    • setTimeout является свободной переменной.
    • g - это локальная переменная.
    • console является переменной свободной.
  • Найдите родительскую область, к которой привязана каждая свободная переменная:
    • i привязан к глобальной области.
    • setTimeout привязан к глобальной области.
    • console привязан к глобальной области.
  • В какой области действия находится функция , на которую ссылаются? Глобальная область .
    • Следовательно, i не закрыто над на f.
    • Следовательно, setTimeout не закрыто над на f.
    • Следовательно, console не замкнуто над на f.

Таким образом, функция f не является замыканием.

Для g:

  • Перечислите переменные:
    • console является переменной свободной.
    • i2 является переменной свободной.
  • Найдите родительскую область, к которой привязана каждая свободная переменная:
    • console привязан к глобальной области.
    • i2 привязан к области f.
  • В какой области действия находится функция , на которую ссылаются? область setTimeout.
    • Следовательно, console не закрыто над на g.
    • Следовательно, i2 закрыто над на g.

Таким образом, функция g является замыканием для свободной переменной i2 (которая является upvalue для g) , когда она указана из setTimeout.

Плохо для вас: ваш друг использует закрытие. Внутренняя функция - это замыкание.

Случай 2: Ваша программа

for (var i = 0; i < 10; i++) {
    setTimeout((function f(i2) {
        return function g() {
            console.log(i2);
        };
    })(i), 1000);
}

В вышеуказанной программе есть две функции: f и g. Посмотрим, закрыты ли они:

Для f:

  • Перечислите переменные:
    • i2 является локальной переменной.
    • g - это локальная переменная.
    • console является переменной свободной.
  • Найдите родительскую область, к которой привязана каждая свободная переменная:
    • console привязан к глобальной области.
  • В какой области действия находится функция , на которую ссылаются? Глобальная область .
    • Следовательно, console не замкнуто над на f.

Таким образом, функция f не является замыканием.

Для g:

  • Перечислите переменные:
    • console является переменной свободной.
    • i2 является переменной свободной.
  • Найдите родительскую область, к которой привязана каждая свободная переменная:
    • console привязан к глобальной области.
    • i2 привязан к области f.
  • В какой области действия находится функция , на которую ссылаются? область setTimeout.
    • Следовательно, console не закрыто над на g.
    • Следовательно, i2 закрыто над на g.

Таким образом, функция g является замыканием для свободной переменной i2 (которая является upvalue для g) , когда она указана из setTimeout.

Хорошо для вас: вы используете закрытие. Внутренняя функция - это замыкание.

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

Изменить: Простое объяснение причин закрытия всех функций (кредиты @Peter):

Сначала рассмотрим следующую программу (это control):

lexicalScope();

function lexicalScope() {
    var message = "This is the control. You should be able to see this message being alerted.";

    regularFunction();

    function regularFunction() {
        alert(eval("message"));
    }
}

Ответ 2

В соответствии с определением closure:

"Закрытие" - это выражение (обычно функция), которое может иметь свободные переменные вместе с средой, которая связывает эти переменные (что "закрывает" выражение).

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

Ответ 3

В двух словах Javascript Closures разрешает функции получать доступ к переменной, которая объявлена ​​в лексико-родительской функции.

Посмотрите более подробное объяснение. Чтобы понять замыкания, важно понять, как переменные области видимости JavaScript.

Прицелы

В JavaScript областях определены функции. Каждая функция определяет новую область.

Рассмотрим следующий пример:

function f()
{//begin of scope f
  var foo='hello'; //foo is declared in scope f
  for(var i=0;i<2;i++){//i is declared in scope f
     //the for loop is not a function, therefore we are still in scope f
     var bar = 'Am I accessible?';//bar is declared in scope f
     console.log(foo);
  }
  console.log(i);
  console.log(bar);
}//end of scope f

вызов f prints

hello
hello
2
Am I Accessible?

Теперь рассмотрим случай, когда мы имеем функцию g, определенную в рамках другой функции f.

function f()
{//begin of scope f
  function g()
  {//being of scope g
    /*...*/
  }//end of scope g
  /*...*/
}//end of scope f

Мы будем называть f лексическим родителем g. Как объяснялось ранее, у нас теперь есть 2 области; область f и область g.

Но одна область видимости "внутри" другой области видимости, так же как и область дочерней функции в области родительской функции? Что происходит с переменными, объявленными в области родительской функции; я смогу получить к ним доступ из сферы действия дочерней функции? То, где именно находятся затворы.

Затворы

В JavaScript функция g может не только получить доступ к любым переменным, объявленным в области g, но и получить доступ к любым переменным, объявленным в области родительской функции f.

Рассмотрим следующее:

function f()//lexical parent function
{//begin of scope f
  var foo='hello'; //foo declared in scope f
  function g()
  {//being of scope g
    var bar='bla'; //bar declared in scope g
    console.log(foo);
  }//end of scope g
  g();
  console.log(bar);
}//end of scope f

вызов f prints

hello
undefined

Посмотрим на строку console.log(foo);. На этом этапе мы находимся в области g, и мы пытаемся получить доступ к переменной foo, объявленной в области f. Но, как указано выше, мы можем получить доступ к любой переменной, объявленной в лексической родительской функции, которая здесь имеет место; g является лексическим родителем f. Поэтому печатается hello.
Теперь посмотрим на строку console.log(bar);. На данный момент мы находимся в области f, и мы пытаемся получить доступ к переменной bar, объявленной в области g. bar не объявляется в текущей области, а функция g не является родительским элементом f, поэтому bar - undefined

Фактически мы также можем получить доступ к переменным, объявленным в области лексической функции "великого родителя". Поэтому, если бы существовала функция h, определенная внутри функции g

function f()
{//begin of scope f
  function g()
  {//being of scope g
    function h()
    {//being of scope h
      /*...*/
    }//end of scope h
    /*...*/
  }//end of scope g
  /*...*/
}//end of scope f

то h сможет получить доступ ко всем переменным, объявленным в области функций h, g и f. Это делается при закрытии. В JavaScript закрытие позволяет нам получить доступ к любой переменной, объявленной в лексической родительской функции, в лексической великой родительской функции, в лексической величественной родительской функции и т.д. Это можно рассматривать как цепочку цепей ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ... до последней родительской функции, которая не имеет лексического родителя.

Объект window

На самом деле цепочка не останавливается на последней родительской функции. Есть еще один особый масштаб; глобальная область . Каждая переменная, не объявленная в функции, считается объявленной в глобальной области. Глобальный охват имеет две специальности:

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

Следовательно, существует всего два способа объявления переменной foo в глобальной области; либо не объявляя его в функции, либо устанавливая свойство foo объекта окна.

Обе попытки используют блокировки

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

Позвольте создать новый язык программирования; JavaScript-No-Closure. Как следует из названия, JavaScript-No-Closure идентичен JavaScript, за исключением того, что он не поддерживает Closures.

Другими словами:

var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello

Хорошо, посмотрим, что произойдет с первым решением с JavaScript-No-Closure;

for(var i = 0; i < 10; i++) {
  (function(){
    var i2 = i;
    setTimeout(function(){
        console.log(i2); //i2 is undefined in JavaScript-No-Closure 
    }, 1000)
  })();
}

поэтому это будет печатать undefined 10 раз в JavaScript-No-Closure.

Следовательно, первое решение использует замыкание.

Посмотрим на второе решение:

for(var i = 0; i < 10; i++) {
  setTimeout((function(i2){
    return function() {
        console.log(i2); //i2 is undefined in JavaScript-No-Closure
    }
  })(i), 1000);
}

поэтому это будет печатать undefined 10 раз в JavaScript-No-Closure.

Оба решения используют закрытие.

Изменить: предполагается, что эти 3 фрагмента кода не определены в глобальной области. В противном случае переменные foo и i будут привязаны к объекту window и поэтому доступны через объект window как в JavaScript, так и в JavaScript-No-Closure.

Ответ 4

Я никогда не был доволен тем, как кто-то объясняет это.

Ключом к пониманию закрытий является понимание того, что будет JS без закрытия.

Без закрытий это вызовет ошибку

function outerFunc(){
    var outerVar = 'an outerFunc var';
    return function(){
        alert(outerVar);
    }
}

outerFunc()(); //returns inner function and fires it

Как только внешнийFunc вернётся в воображаемой закрытой-отключенной версии JavaScript, ссылка на внешнийVar будет собирать мусор и не оставлять ничего там, где внутренняя функция ссылается.

Закрытие - это, по сути, специальные правила, которые пинают и позволяют, чтобы эти вары существовали, когда внутренняя функция ссылается на внешние переменные функции. С закрытием ссылки, обозначенные варами, поддерживаются даже после выполнения внешней функции или "закрыты", если это помогает вам запомнить эту точку.

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

Как только у вас есть ссылка во внутреннем func внешнем var, однако это, как дверной косяк, помещается на пути сбора мусора для упомянутых vars.

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

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

function outerFunc(){
    var incrementMe = 0;
    return function(){ incrementMe++; console.log(incrementMe); }
}
var inc = outerFunc();
inc(); //logs 1
inc(); //logs 2

Ответ 5

Вы оба используете закрытие.

Я собираюсь с определение Википедии здесь:

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

В попытке вашего друга явно используется переменная i, которая является нелокальной, принимая ее значение и делая копию для хранения в локальном i2.

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

Ответ 6

Вы и ваш друг используете закрытие:

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

MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures

В вашей кодовой функции друга function(){ console.log(i2); }, определенной внутри закрытия анонимной функции function(){ var i2 = i; ..., и может читать/записывать локальную переменную i2.

В вашей функции кода function(){ console.log(i2); }, определенной внутри закрытия функции function(i2){ return ..., и может читать/записывать локальный ценный i2 (объявленный в этом случае как параметр).

В обоих случаях функция function(){ console.log(i2); } затем переходит в setTimeout.

Другой эквивалент (но с меньшим использованием памяти):

function fGenerator(i2){
    return function(){
        console.log(i2);
    }
}
for(var i = 0; i < 10; i++) {
    setTimeout(fGenerator(i), 1000);
}

Ответ 7

Посмотрите на оба пути:

(function(){
    var i2 = i;
    setTimeout(function(){
        console.log(i2);
    }, 1000)
})();

Объявляет и немедленно выполняет анонимную функцию, которая запускает setTimeout() в своем собственном контексте. Текущее значение i сохраняется, сначала сделав копию в i2; он работает из-за немедленного выполнения.

setTimeout((function(i2){
    return function() {
        console.log(i2);
    }
})(i), 1000);

Объявляет контекст выполнения для внутренней функции, при которой текущее значение i сохраняется в i2; этот подход также использует немедленное выполнение для сохранения значения.

Внимание!

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

Обтекание обоих кодов внутри другого setTimeout() не доказывает, что только второй подход использует закрытие, с самого начала не то же самое.

Заключение

Оба метода используют закрытие, поэтому оно сводится к личным вкусам; второй подход легче "перемещать" или обобщать.

Ответ 8

Закрытие

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

Иными словами: замыкание - это копия соответствующего контекста переменных, от которых зависит функция.

Еще раз (naïf): закрытие имеет доступ к переменным, которые не передаются в качестве параметра.

Помните, что эти функциональные концепции сильно зависят от языка программирования/среды, которую вы используете. В JavaScript закрытие зависит от лексического охвата (что истинно на большинстве c-языков).

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

Итак, в отношении ваших примеров:

// 1
for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i); // closure, only when loop finishes within 1000 ms,
    }, 1000);           // i = 10 for all functions
}
// 2
for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i; // closure of i (lexical scope: for-loop)
        setTimeout(function(){
            console.log(i2); // closure of i2 (lexical scope:outer function)
        }, 1000)
    })();
}
// 3
for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2); // closure of i2 (outer scope)

        }
    })(i), 1000); // param access i (no closure)
}

Все используют замыкания. Не путайте точку исполнения с закрытием. Если "моментальный снимок" замыканий берется в неподходящий момент, значения могут быть неожиданными, но, конечно же, происходит закрытие!

Ответ 9

Я написал это некоторое время назад, чтобы напомнить себе, что такое закрытие и как оно работает в JS.

Закрытие - это функция, которая при вызове использует область, в которой она была объявлена, а не область, в которой она была вызвана. В javaScript все функции ведут себя так. Значения переменных в области действия сохраняются до тех пор, пока есть функция, которая все еще указывает на них. Исключением из правила является 'this', который относится к объекту, внутри которого функция находится, когда она вызывается.

var z = 1;
function x(){
    var z = 2; 
    y(function(){
      alert(z);
    });
}
function y(f){
    var z = 3;
    f();
}
x(); //alerts '2' 

Ответ 10

После тщательного осмотра, похоже, что вы используете закрытие.

В случае с друзьями, i открывается внутри анонимной функции 1 и i2 открывается в анонимной функции 2, где присутствует console.log.

В вашем случае вы получаете доступ к i2 внутри анонимной функции, где присутствует console.log. Добавьте оператор debugger; до console.log и в инструментах разработчика Chrome в разделе "Scope variables" он скажет, в какой области видимости переменная.

Ответ 11

Рассмотрим следующее. Это создает и воссоздает функцию f, которая закрывается на i, но отличается от нее::

i=100;

f=function(i){return function(){return ++i}}(0);
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

f=function(i){return new Function('return ++i')}(0);        /*  function declarations ~= expressions! */
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));