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