Когда на самом деле создается закрытие? - PullRequest
13 голосов
/ 06 мая 2010

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

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

Ответы [ 5 ]

8 голосов
/ 06 мая 2010

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

<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>

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

.
<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() {}.
1 голос
/ 06 мая 2010

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

Да.

0 голосов
/ 16 ноября 2015

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

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

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

Итак,

function foo() { }

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

var fn = function() { };

Или

return function() { return 1; };

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

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

0 голосов
/ 21 мая 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): в глобальной среде (где выполняется var i = 10; выполняется сопоставление от 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.Оказывается, мы находим это в текущей среде, поэтому нам не нужно просматривать родительскую среду.Затем мы возвращаем это значение.Теперь новая среда, которую мы только что создали, будет собирать мусор, поскольку мы больше не сможем достичь этой среды по любому пути из глобального.

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

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

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

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

0 голосов
/ 21 мая 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...