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

562

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

Давайте возьмем этот пример. У нас есть цикл подсчета, и мы хотим напечатать переменную counter на консоли с задержкой. Поэтому мы используем 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 в другое 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 на консоли с задержкой, поэтому они решают исходную проблему, но мы хотим понять, какое из этих двух решений использует замыкания для достижения этой цели.

leemes
источник
1
@leemes: см. мой ниндзя редактировать для второй ссылки.
Блендер
2
мы только что заключили сделку: тот, кто прав, собирается получить SO баллы, связанные с этим вопросом
Brillout
1
@leemes - вы оба используете замыкания. Вы оба сделали две функции - внешнюю функцию и внутреннюю функцию; и обе ваши внутренние функции - замыкания. Все ваши функции - лямбды ( анонимные функции ). Прочитайте мой ответ для деталей.
Аадит М Шах
1
@blesh - я понятия не имею, что такое измененное закрытие. Я вижу, что ваша ссылка указывает на код C #. Поддерживаются ли модифицированные замыкания JavaScript?
Аадит М Шах

Ответы:

650

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

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

  1. Возьми функцию. Давайте назовем это F.
  2. Перечислите все переменные F.
  3. Переменные могут быть двух типов:
    1. Локальные переменные (связанные переменные)
    2. Нелокальные переменные (свободные переменные)
  4. Если F не имеет свободных переменных, то оно не может быть замыканием.
  5. Если F имеет какие - либо свободные переменные (которые определены в а родительского объема F) , то:
    1. Там должен быть только один родительский объем F , к которому свободная переменная связана.
    2. Если на F ссылаются из-за пределов этой родительской области, то это становится закрытием для этой свободной переменной.
    3. Эта свободная переменная называется повышением значения замыкания F.

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

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

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

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

Для f:

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

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

Для g:

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

Таким образом, функция gявляется закрытием для свободной переменной i2(которая является повышением значения 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:

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

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

Для g:

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

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

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

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

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

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

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"));
    }
}

  1. Мы знаем, что оба lexicalScopeи regularFunctionне являются замыканиями из приведенного выше определения .
  2. Когда мы выполняем программу, мы ожидаем, что получим message предупреждение, потому что regularFunction это не закрытие (то есть она имеет доступ ко всем переменным в родительской области, включаяmessage ).
  3. Когда мы выполняем программу, мы наблюдаем, что messageэто действительно насторожило.

Далее давайте рассмотрим следующую программу (это альтернатива ):

var closureFunction = lexicalScope();

closureFunction();

function lexicalScope() {
    var message = "This is the alternative. If you see this message being alerted then in means that every function in JavaScript is a closure.";

    return function closureFunction() {
        alert(eval("message"));
    };
}

  1. Мы знаем, что это только closureFunctionзамыкание из приведенного выше определения .
  2. Когда мы выполняем программу, мы ожидаем, что message не будем предупреждены, потому что closureFunction это закрытие (то есть она имеет доступ ко всем своим нелокальным переменным во время создания функции ( см. Этот ответ ) - это не включает message).
  3. Когда мы выполняем программу, мы видим, что messageна самом деле это предупреждение.

Что мы выводим из этого?

  1. Интерпретаторы JavaScript не обрабатывают замыкания иначе, чем другие функции.
  2. Каждая функция несет свою цепочку областей действия вместе с ней. У замыканий нет отдельной среды ссылок.
  3. Закрытие, как и любая другая функция. Мы просто называем их замыканиями, когда на них ссылаются за пределами области, к которой они принадлежат, потому что это интересный случай.
Адит М Шах
источник
40
Принято, потому что вы идете очень подробно, очень хорошо объясняя, что происходит. И наконец, теперь я лучше понял, что такое замыкание, или лучше сказал: как работает привязка переменных в JS.
Leemes
3
В случае 1 вы говорите, что он gработает в области действия setTimeout, но в случае 2 вы говорите, что он fработает в глобальной области действия. Они оба находятся внутри setTimeout, так в чем же разница?
rosscj2533
9
Не могли бы вы указать свои источники для этого? Я никогда не видел определения, где функция может быть замыканием, если она вызывается в одной области, но не в другой. Таким образом, это определение выглядит как подмножество более общего определения, к которому я привык (см . Ответ kev ), где замыкание является замыканием, является замыканием независимо от того, в какой области оно вызывается, или даже если оно никогда не вызывается!
Briguy37
11
@AaditMShah Я согласен с вами относительно того, что такое замыкание, но вы говорите так, как будто есть разница между обычными функциями и замыканиями в JavaScript. Нет никакой разницы; внутренне каждая функция будет иметь ссылку на конкретную цепочку областей действия, в которой она была создана. Двигатель JS не считает это другим случаем. Нет необходимости в сложном контрольном списке; Просто знайте, что каждый объект функции несет лексическую область видимости. Тот факт, что переменные / свойства доступны глобально, не делает функцию менее замкнутой (это просто бесполезный случай).
Питер
13
@Peter - Знаешь что, ты прав. Там нет разницы между обычной функцией и замыканием. Я провел тест, чтобы доказать это, и это привело к вашей пользе: вот контроль, а вот альтернатива . То, что вы говорите, имеет смысл. Интерпретатор JavaScript должен вести специальную бухгалтерию для замыканий. Это просто побочные продукты лексически ограниченного языка с функциями первого класса. Мои знания были ограничены тем, что я читал (что было ложным). Спасибо, что поправили меня. Я обновлю свой ответ, чтобы отразить то же самое.
Аадит М Шах
96

Согласно closureопределению:

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

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

кэв
источник
1
Как в третьей версии используется переменная, определенная вне функции?
Джон
1
@Jon использование возвращенной функции, i2которая определена снаружи.
Кев
1
@kev Вы используете замыкание, если вы определяете функцию, которая использует переменную, которая определена вне функции ...... тогда в «Случай 1: Программа вашего друга» «Аадит М Шах» ответ - «функция f». закрытие? он использует i (переменная, которая определяется вне функции). глобальная область ссылается на определитель?
внутренние органы -
54

В двух словах, 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 печатает

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 печатает

hello
undefined

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

На самом деле мы также можем получить доступ к переменным, объявленным в области действия лексической функции «прародителя». Поэтому, если бы была функция, 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объекта.

Поэтому существует ровно два способа объявления переменной 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)
  })();
}

поэтому это будет печатать undefined10 раз в 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);
}

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

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

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

brillout.com
источник
Почему должно iбыть неопределенным? Вы просто ссылаетесь на родительскую область, которая все еще действует, если не было замыканий.
Leemes
по той же причине, что foo не определена в JavaScript-No-Closure. <code> i </ code> не является неопределенным в JavaScript благодаря функции в JavaScript, которая позволяет получить доступ к переменным, определенным в лексическом родительском элементе. Эта функция называется закрытием.
Brillout
Вы не поняли разницу между ссылкой на уже определенные переменные и свободными переменными. В замыканиях мы определяем свободные переменные, которые должны быть связаны во внешнем контексте. В коде, вы просто установить i2 в iто время , когда вы определяете функцию. Это делает iНЕ свободной переменной. Тем не менее, мы считаем вашу функцию закрытой, но без какой-либо свободной переменной, в этом все дело.
Leemes
2
@leemes, я согласен. И по сравнению с принятым ответом, это на самом деле не показывает, что на самом деле происходит. :)
Авель
3
я думаю, что это лучший ответ, объясняющий замыкания в целом и просто, а затем переходящий к конкретному варианту использования. Спасибо!
Тим Петерсон
22

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

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

Без замыканий это приведет к ошибке

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

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

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

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

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

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

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

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

function outerFunc(){
    var incrementMe = 0;
    return function(){ incrementMe++; console.log(incrementMe); }
}
var inc = outerFunc();
inc(); //logs 1
inc(); //logs 2
Эрик Реппен
источник
Вы правы насчет «снимка» (я думаю, вы ссылаетесь на мой ответ) по этому поводу. Я искал слово, которое указывало бы на поведение. В вашем примере это можно рассматривать как конструкцию замыкания «горячей ссылки». При обнаружении замыкания в качестве параметра во внутренней функции можно утверждать, что оно ведет себя как «снимок». Но я согласен, неправильно используемые слова только добавляют путаницу к предмету. Если у вас есть какие-либо предложения по этому поводу, я обновлю свой ответ.
Андриес
Это может помочь в объяснении, если вы дадите внутренней функции именованную функцию.
Филипп Сенн
Без замыканий вы получите ошибку, потому что вы пытаетесь использовать переменную, которая не существует.
Хуан Мендес
Хм ... хорошая мысль. Разве ссылка на неопределенную переменную никогда не выдает ошибку, поскольку в конечном итоге она будет выглядеть как свойство глобального объекта, или я путаю назначение с неопределенными переменными?
Эрик Реппен
17

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

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

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

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

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

Джон
источник
Да, но я думаю, дело в том, как он это делает. Он просто копирует iв i2, а затем определяет некоторую логику и выполняет эту функцию. Если бы я не выполнил это немедленно, но сохранил бы это в переменной и выполнил бы это после цикла, это напечатало бы 10, не так ли? Так что это не захватило меня.
Leemes
6
@leemes: захватил iпросто отлично. Поведение, которое вы описываете, не является результатом замыкания против незакрытия; это результат изменения закрытой переменной за это время. Вы делаете то же самое, используя другой синтаксис, немедленно вызывая функцию и передавая ее iв качестве аргумента (которая сразу копирует ее текущее значение). Если вы положите свой собственный setTimeoutв другой, setTimeoutто же самое произойдет.
Джон
13

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

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

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);
}
Андрей Д.
источник
1
Я не понимаю, почему ваше решение против решения моего друга «быстрее и с меньшим использованием памяти», не могли бы вы уточнить?
Brillout
3
В вашем решении вы создаете 20 функциональных объектов (2 объекта в каждом цикле: 2x10 = 20). Тот же результат в решении вашего друга. В «моем» решении создается только 11 функциональных объектов: 1 перед циклом for и 10 «внутрь» - 1 + 1x10 = 11. Как результат - меньшее использование памяти и увеличение скорости.
Эндрю Д.
1
В теории это было бы правдой. На практике также: См. Этот тест JSPerf
Роб W
10

закрытие

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

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

Еще раз (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)
}

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

Andries
источник
10

Давайте посмотрим на оба пути:

(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()не доказывает, что только во втором подходе используются замыкания, для начала просто не то же самое.

Вывод

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

Джек
источник
Я думаю, что разница в следующем: его решение (1-е) захватывает по ссылке, мое (2-е) захватывает по значению. В этом случае это не имеет значения, но если бы я поместил выполнение в другой setTimeout, мы бы увидели, что у его решения есть проблема, заключающаяся в том, что он использует окончательное значение i, а не текущее, в то время как мой подоконник использует текущее значение (так как захвачено значением).
Leemes
@leemes Вы оба снимаете одинаково; Передача переменной через аргумент функции или присваивание - это то же самое ... не могли бы вы добавить к своему вопросу, как бы вы завершили выполнение в другом setTimeout()?
Ja͢ck
позвольте мне проверить это ... Я хотел показать, что объект функции может быть передан и исходная переменная iможет быть изменена, не влияя на то, что функция должна вывести, независимо от того, где и когда мы ее выполняем.
Leemes
Подождите, вы не передали функцию (внешнему) setTimeout. Удалите их (), передав таким образом функцию, и вы увидите 10 раз результат 10.
Leemes
@leemes Как упоминалось ранее, ()это именно то, что заставляет его код работать так же, как ваш (i); вы не просто обернули его код, вы внесли в него изменения ... поэтому вы больше не можете сделать правильное сравнение.
Ja͢ck
8

Я написал это некоторое время назад, чтобы напомнить себе, что такое замыкание и как оно работает в 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' 
Нат Дарке
источник
6

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

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

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

Рамеш
источник
2
Раздел «Закрытие» на правой панели используется, потому что нет более конкретного имени. «Местный» является более сильным показателем, чем «Закрытие».
Роб W
4

Учтите следующее. Это создает и воссоздает функцию, 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'));

в то время как следующее закрывает саму «функцию» «
(сами! фрагмент после этого использует один референт f)

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

или чтобы быть более явным:

for(var i = 0; i < 10; i++) {
    console.log(    f = new Function( 'console.log('+i+')' )    );
    setTimeout( f,  1000 );
}

NB. последнее определение fIS , function(){ console.log(9) } прежде чем 0 будет напечатано.

Предостережение! Концепция замыкания может отвлекать нас от сути элементарного программирования:

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

x-refs .:
Как работают JavaScript-замыкания?
Javascript Closures Объяснение
Требует ли (JS) замыкания функция внутри функции
Как понимать замыкания в Javascript?
Путаница в локальной и глобальной переменной Javascript

Ekim
источник
фрагменты пробовали в первый раз - не уверен, как управлять - Run' only was desired - not sure how to remove the Копировать`
ekim
-1

Я хотел бы поделиться своим примером и объяснением закрытия. Я сделал пример с Python и две фигуры для демонстрации состояния стека.

def maker(a, b, n):
    margin_top = 2
    padding = 4
    def message(msg):
        print('\n * margin_top, a * n, 
            ' ‘ * padding, msg, '  * padding, b * n)
    return message

f = maker('*', '#', 5)
g = maker('', '♥’, 3)

f('hello')
g(‘good bye!')

Вывод этого кода будет следующим:

*****      hello      #####

      good bye!    ♥♥♥

Вот два рисунка, на которых показаны стеки и замыкание, прикрепленные к объекту функции.

когда функция возвращается от создателя

когда функция вызывается позже

Когда функция вызывается через параметр или нелокальную переменную, коду нужны привязки локальной переменной, такие как margin_top, padding, а также a, b, n. Для того, чтобы код функции работал, должен быть доступен стековый фрейм функции maker, который давно отсутствовал, который поддерживается в замыкании, которое мы можем найти вместе с объектом сообщения функции.

Eunjung Lee
источник
Я хотел бы удалить этот ответ. Я понял, что вопрос не в том, что такое закрытие, поэтому я хотел бы перенести его на другой вопрос.
Eunjung Lee
2
Я считаю, что у вас есть возможность удалить свой собственный контент. Нажмите на deleteссылку под ответом.
Рори МакКроссан