Если я могу предложить модель того, когда и как создаются замыкания (это обсуждение является теоретическим, в действительности интерпретатор может делать что угодно, если конечный результат одинаков): замыкание создается всякий раз, когда функция оценивается во время выполнение. Закрытие будет указывать на среду, в которой происходит выполнение. Когда сайт загружается, 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, вСначала я хотел написать короткий ответ, но, оказывается, это бегемоты.Надеюсь, я не совершаю явной ошибки.