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

12 ответов


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

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

  1. принять функция. Назовем его Ф.
  2. перечислите все переменные F.
  3. переменные могут быть двух типов:
    1. локальные переменные (связанные переменные)
    2. нелокальные переменные (свободные переменные)
  4. если F не имеет свободных переменных, то это не может быть замыканием.
  5. если F имеет какие-либо свободные переменные (которые определены в a родительская область F) тогда:
    1. должна быть только одна родительская область из F к которому a свободная переменная привязана.
    2. если F ссылка извне это родительская область, затем она становится закрытием для это свободной переменной.
    3. это свободная переменная называется значением 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:

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

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

на g:

  1. список переменных:
    1. console это свободный переменной.
    2. i2 это свободный переменной.
  2. найдите родительскую область, к которой привязана каждая свободная переменная:
    1. console is привязан на глобальном уровне.
    2. i2 is привязан в рамках f.
  3. в какой области находится функция ссылка? The объем setTimeout.
    1. отсюда console не закрыт by g.
    2. отсюда i2 is закрыт by 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:

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

таким образом, функция f - это не закрытие.

на g:

  1. список переменных:
    1. console это свободный переменной.
    2. i2 это свободный переменной.
  2. найдите родительскую область, к которой привязана каждая свободная переменная:
    1. console is привязан на глобальном уровне.
    2. i2 is привязан в рамках f.
  3. в какой области находится функция ссылка? The объем setTimeout.
    1. отсюда console не закрыт by g.
    2. отсюда i2 is закрыт by g.
функции g является закрытием для свободной переменной i2 (что является upvalue для g) , когда это ссылка внутри setTimeout.

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

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

Edit: простой объяснение, почему все закрытия функций (credits @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 является закрытием (т. е. он имеет доступ только ко всем его нелокальные переменные at время создания функции (посмотреть этот ответ) - это не относится к message).
  3. когда мы выполняем программу мы видим это message фактически предупрежден.

что же мы делаем вывод из это?

  1. интерпретаторы JavaScript не обрабатывают замыкания иначе, чем они обрабатывают другие функции.
  2. каждая функция несет свою объем цепи вместе с ним. Замыканий нет отдельные среда ссылок.
  3. закрытие так же, как и любая другая функция. Мы просто называем их закрытиями, когда они ссылка в объеме за пределами объем к которому они принадлежать , потому что это интересный случай.

По словам closure определение:

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

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


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

давайте посмотрим более подробное объяснение. Чтобы понять закрытие, важно понять, как 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

вызов Ф печать

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 на лексические родитель of 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

вызов Ф печать

hello
undefined

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

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

объект окна

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

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

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

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

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

    другими словами;

    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-нет закрытия.

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

    давайте посмотрим на второе решение;

    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-нет закрытия.

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

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


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

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

без замыканий это вызовет ошибку

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

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

после того, как outerFunc вернулся в воображаемую версию JavaScript с отключенным закрытием, ссылка на outerVar была бы собранным мусором и ушла, не оставив ничего для внутреннего func ссылка.

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

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

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

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

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

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

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

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

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

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

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


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

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

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 (указанный в данном случае в качестве параметра).

в обоих случаях функция перешел в setTimeout.

другой аналогичный (но с меньшим использованием памяти):

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

закрытие

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

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

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

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


давайте посмотрим в обе стороны:

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

вывод

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


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

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

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

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

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

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


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

в то время как следующее закрывается на "A" функция "сама"
( сами! фрагмент после этого использует один referent 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. последнее определение f is function(){ console.log(9) } до 0 печати.

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

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

х-рефов.:
как работают закрытия JavaScript?
Объяснение Закрытия Javascript
требует ли закрытие (JS) функции внутри функции
Как понять закрытие в В JavaScript?
Javascript локальная и глобальная переменная путаница


Я хотел бы поделиться своим примером и объяснением о закрытиях. Я сделал пример 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!    ♥♥♥

вот две цифры, чтобы показать, стеки и закрытие привязан к объекту функции.

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

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

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