Когда на самом деле создается замыкание?

Верно ли, что замыкание создается в следующих случаях для foo, но не для bar?

Дело 1:

<script type="text/javascript">

    function foo() { }

</script>

foo — это замыкание с цепочкой областей действия только с глобальной областью действия.

Случай 2:

<script type="text/javascript">

    var i = 1;
    function foo() { return i; }

</script>

то же, что Случай 1.

Случай 3:

<script type="text/javascript">

    function Circle(r) {
        this.r = r;
    }
    Circle.prototype.foo = function() { return 3.1415 * this.r * this.r }

</script>

в этом случае Circle.prototype.foo (который возвращает площадь круга) относится к замыканию только с глобальной областью действия. (это замыкание создано).

Случай 4:

<script type="text/javascript">

    function foo() { 
        function bar() { 
        }
    }

</script>

здесь foo — это замыкание только с глобальной областью действия, но bar не является замыканием (пока), потому что функция foo не вызывается в коде, поэтому замыкание bar никогда не создается. Он будет существовать только в том случае, если будет вызван foo, а замыкание bar будет существовать до тех пор, пока foo не вернётся, а замыкание bar потом будет собрано сборщиком мусора, так как ссылки на него вообще нигде нет.

Поэтому, когда функция не существует, не может быть вызвана, на нее нельзя сослаться, замыкание еще не существует (еще не создано). Только когда функция может быть вызвана или на нее можно сослаться, замыкание действительно создается?


person nonopolarity    schedule 06.05.2010    source источник
comment
Я считаю, что мы можем только догадываться. Это детали реализации, а не особенности языка.   -  person Ionuț G. Stan    schedule 06.05.2010
comment
В новой книге Джона Резига, Secrets of a JS Ninja, есть действительно хороший обзор закрытия - он также проходит через многое из этого есть на его сайте, и его стоит прочитать — я был бы удивлен, если бы вы не не узнать что-то.   -  person Martyn    schedule 06.05.2010
comment
Все, что вы говорите, правильно, если вы заменяете каждое появление слова «замыкание» словом «функция». Объяснение того, что такое замыкание, см. в моем ответе.   -  person Alsciende    schedule 07.05.2010


Ответы (5)


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

<script type="text/javascript">
    var i = 1;
    function foo() { return i; }
</script>

Здесь i — свободная переменная для функционального кода foo. И эта свободная переменная не привязана к какому-либо конкретному значению каким-либо существующим контекстом (замыканием). Так что у вас нет замыкания.

<script type="text/javascript">
    var i = 1;
    function foo() { return i; }
    foo(); // returns 1
    i = 2;
    foo(); // returns 2
</script>

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

<script type="text/javascript">

    function bar() {
       var i = 1;
       function foo() { return i; }
       return foo;
    }
    bar(); // returns function foo() { return i; }
    bar()(); // returns 1
    // no way to change the value of the free variable i => bound => closure
</script>

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

<script type="text/javascript">

    function bar() {
       var i = 1;
       function foo() { return i; }
       i = 2;
       return foo;
    }
    bar()(); // returns 2
</script>

Что касается ваших примеров:

  1. Случай 1 — это не замыкание, это просто функция
  2. Случай 2 — это не замыкание, это другая функция со свободной переменной
  3. Случай 3 — это не замыкание, это еще одна функция со специальной «переменной» this. Когда функция вызывается как член объекта, объекту присваивается значение this. В противном случае значение this является глобальным объектом.
  4. Случай 4 — это не замыкание, это функция, определенная внутри другой функции. Если foo возвращает bar, вы должны создать замыкание, содержащее только «bar» и его значение: function bar() {}.
person Alsciende    schedule 06.05.2010
comment
Теперь представьте, что программа вызывает moo.f, а moo.f выполняет this.g = foo, а затем this.g(). Разве не правда, что foo — это функция, несущая в себе контекст глобальной области видимости? Тогда это подходит под определение замыкания? - person nonopolarity; 06.05.2010
comment
Функция не может быть привязана к контексту глобальной области видимости, так как вы никогда не сможете вернуться из глобальной области видимости. Таким образом, он никогда не закрывается, поэтому вы не можете закрыть его. - person Alsciende; 07.05.2010
comment
вам нужно вернуть его, чтобы он был закрыт? Я не думаю, что вы должны вернуть его. вы можете назначить его и почему это имеет значение? и я думаю, что определение закрытия: какой-то код для запуска вместе с его контекстом, в данном случае, глобальная область действия, будет соответствовать случаям, прежде всего вместе. - person nonopolarity; 08.05.2010
comment
Хотя во втором примере я должен вызывать foo в среде, где i имеет совершенно другое значение. Это по-прежнему будет ссылаться на i в глобальной области видимости. Даже если я вызову его в функции, которая затеняет i каким-то другим i. - person Zorf; 21.05.2010
comment
Я думаю, вы говорите о прицелах. Замыкания — это немного больше, чем области видимости, это способ извлечь функцию из области видимости, чтобы заморозить значения. - person Alsciende; 04.05.2011
comment
...для замораживания значений. Но замыкания не замораживают значения. Переменные в замыкании остаются активными, и им могут быть присвоены новые значения. В этом весь смысл. вы не можете иметь замыкание, если функция не возвращает другую функцию - да, вы можете, если некоторая ссылка на внутреннюю функцию доступна вне содержащей функции: возврат внутренней функции - это только один из способов сделать это (но это может, например, произойти через присваивание или в параметре обратного вызова). - person nnnnnn; 22.06.2012

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

да.

person Delan Azabani    schedule 06.05.2010
comment
но что, если foo никогда не вызывается, значит, панель закрытия никогда не создавалась? - person nonopolarity; 06.05.2010

Ни в одном из этих примеров не создается замыкание.

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

Закрытие — это просто функция, которая ссылается на переменные из своей среды создания в своем теле, каноническим примером является сумматор:

function createAdder(x) { //this is not a closure
    return function(y) { //this function is the closure however, it closes over the x.
        return y + x;
    }
} //thus createAdder returns a closure, it's closed over the argument we put into createAdder

var addTwo = createAdder(2);

addTwo(3); //3
person Zorf    schedule 21.05.2010

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

function f(<vars>) {
  <body>
}

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

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

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

// At global level
var i = 10;                  // (1)
function make_counter(start) {
  return function() {
    var value = start++;
    return value;
  };
}                            // (2)
var count = make_counter(10);    // (3)
count();  // return 10       // (4)
count();  // return 11       // (5)
count = 0;                   // (6)

Что происходит, так это:

В точке (1): ассоциация от i до 10 создается в глобальной среде (где выполняется var i = 10;.

В точке (2): выполняется замыкание с переменной (start) и телом return ...;, которое указывает на среду, в которой оно выполняется (глобальное). Затем создается ассоциация от make_counter к замыканию, которое мы только что создали.

В точке (3): происходит несколько интересных вещей. Сначала мы находим, с чем связано make_counter в глобальной среде. Затем мы выполняем это закрытие. Следовательно, создается новая среда, назовем ее CE, которая указывает на среду, на которую указывает замыкание make_counter (глобальная). Затем мы создаем ассоциацию от start до 10 в CE и запускаем тело замыкания make_counter в CE. Здесь мы сталкиваемся с другой функцией, которая является анонимной. Однако происходит то же самое, что и раньше (напомним, что function f() {} эквивалентно var f = function() {};). Замыкание, назовем его count, создается с помощью переменной () (пустой список) и тела var ... return value;. Теперь это закрытие будет указывать на среду, в которой оно выполняется, то есть CE. Это будет очень важно в дальнейшем. Наконец, у нас есть count, указывающее на новое замыкание в глобальной среде (почему глобальное? Потому что var count ... выполняется в глобальной среде). Заметим, что CE не подвергается сборке мусора, потому что мы можем получить доступ к CE через замыкание make_counter, к которому мы можем обратиться из глобальной среды из переменной make_counter.

В точке (4) происходит более интересное. Сначала мы находим замыкание, связанное с count, которое мы только что создали. Затем мы создаем новую среду, родителем которой является среда, на которую указывает замыкание, то есть CE! Мы выполняем тело замыкания в этой новой среде. Когда выполняется var value = start++;, мы ищем переменную start, начиная с текущей среды и последовательно продвигаясь вверх к глобальной среде. Мы нашли start в окружении CE. Мы увеличиваем значение этого start, первоначально 10 до 11. Теперь start в CE указывает на значение 11. Когда мы сталкиваемся с var value, это означает, что не нужно искать существующий value, а просто создать переменную в среде, где она выполняется. Таким образом, создается ассоциация от value до 11. В return value; мы ищем value так же, как мы искали start. Оказывается, мы находим его в текущей среде, поэтому нам не нужно просматривать родительские среды. Затем мы возвращаем это значение. Теперь новая среда, которую мы только что создали, будет удалена сборщиком мусора, так как мы больше не можем получить доступ к этой среде ни по какому пути из global.

В точке (5) происходит то же самое, что и выше. Но теперь, когда мы ищем start, мы обнаружили, что значение равно 11 вместо 10 (в среде CE).

В точке (6) мы переназначаем count в глобальном окружении. Мы обнаружили, что теперь мы больше не можем найти путь от глобального к замыканию count и, в свою очередь, больше не можем найти путь к окружению CE. Следовательно, оба из них будут собирать мусор.

P.S. Для тех, кто знаком с LISP или Scheme, приведенная выше модель точно такая же, как модель среды в LISP/Scheme.

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

person Chris Henry    schedule 21.05.2010
comment
так что вы думаете, что случаи с 1 по 4 в исходном вопросе тоже являются замыканиями? Я согласен с вами после изучения того, что такое замыкание в Ruby или Lisp. - person nonopolarity; 16.11.2015

На самом деле, после нескольких лет использования JavaScript и довольно тщательного его изучения у меня теперь есть лучший ответ:

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

Поскольку функция — это просто объект, мы можем точнее сказать, что всякий раз, когда создается экземпляр объекта Function (экземпляр функции возникает), создается замыкание.

So,

function foo() { }

Когда JS завершает выполнение вышеуказанной строки, уже есть замыкание или

var fn = function() { };

Or

return function() { return 1; };

Почему? Поскольку замыкание — это просто функция с цепочкой областей видимости, поэтому в каждой приведенной выше ситуации функция существовала (она возникла. Вы можете вызвать ее (вызвать ее)). У него тоже был размах. Итак, в моем первоначальном вопросе (я был ОП) в каждом случае с 1 по 4 было создано закрытие в каждом отдельном случае.

Случай 4 — интересный случай. После запуска этого кода происходит замыкание из-за появления foo(), но bar() еще не существует (без вызова foo()), поэтому было создано одно замыкание, а не два.

person nonopolarity    schedule 16.11.2015