Закрытие - это функция и область действия этой функции.
Это помогает понять, как Javascript реализует область действия в этом случае. На самом деле это всего лишь серия вложенных словарей. Рассмотрим этот код:
var global1 = "foo";
function myFunc() {
var x = 0;
global1 = "bar";
}
myFunc();
Когда программа запускается, у вас есть один словарь области видимости, глобальный словарь, в котором может быть определено несколько вещей:
{ global1: "foo", myFunc:<function code> }
Скажем, вы вызываете myFunc, у которого есть локальная переменная x. Новая область создается для выполнения этой функции. Локальная область действия функции выглядит следующим образом:
{ x: 0 }
Он также содержит ссылку на родительскую область видимости. Таким образом, вся область действия функции выглядит следующим образом:
{ x: 0, parentScope: { global1: "foo", myFunc:<function code> } }
Это позволяет myFunc изменять global1. В Javascript, когда вы пытаетесь присвоить значение переменной, он сначала проверяет локальную область для имени переменной. Если он не найден, он проверяет parentScope, parentScope этой области и т. Д., Пока не будет найдена переменная.
Закрытие - это буквально функция плюс указатель на область действия этой функции (которая содержит указатель на родительскую область видимости и т. Д.). Итак, в вашем примере, после завершения цикла for
область действия может выглядеть следующим образом:
setupHelpScope = {
helpText:<...>,
i: 3,
item: {'id': 'age', 'help': 'Your age (you must be over 16)'},
parentScope: <...>
}
Каждое создаваемое вами закрытие будет указывать на этот единственный объект области видимости. Если бы мы перечислили каждое созданное вами замыкание, оно выглядело бы примерно так:
[anonymousFunction1, setupHelpScope]
[anonymousFunction2, setupHelpScope]
[anonymousFunction3, setupHelpScope]
Когда выполняется любая из этих функций, она использует переданный ей объект области действия - в данном случае это один и тот же объект области действия для каждой функции! Каждый из них будет смотреть на одну и ту же переменную item
и видеть одно и то же значение, которое является последним, установленным вашим циклом for
.
Чтобы ответить на ваш вопрос, не имеет значения, добавляете ли вы var item
над циклом for
или внутри него. Поскольку циклы for
не создают свою собственную область, item
будет храниться в словаре областей текущей функции, который равен setupHelpScope
. Корпуса, созданные внутри цикла for
, всегда будут указывать на setupHelpScope
.
Некоторые важные заметки:
- Это происходит потому, что в Javascript циклы
for
не имеют собственной области видимости - они просто используют область действия включающей функции. Это также верно для if
, while
, switch
и т. Д. Если бы это был C #, с другой стороны, для каждого цикла был бы создан новый объект области, и каждое замыкание содержало бы указатель на свой собственный уникальный объем.
- Обратите внимание, что если
anonymousFunction1
изменяет переменную в своей области, она изменяет эту переменную для других анонимных функций. Это может привести к некоторым действительно странным взаимодействиям.
- Области действия - это просто объекты, подобные тем, которые вы программируете. В частности, это словари. Виртуальная машина JS управляет их удалением из памяти, как и все остальное - с помощью сборщика мусора. По этой причине чрезмерное использование замыканий может привести к увеличению объема памяти. Поскольку замыкание содержит указатель на объект области действия (который, в свою очередь, содержит указатель на его родительский объект области действия и так далее и далее), вся цепочка области действия не может быть собрана мусором и должна оставаться в памяти.
Дальнейшее чтение: