Нужно ли ссылаться на переменные, чтобы они были включены в замыкание? - PullRequest
2 голосов
/ 21 июля 2011

При создании замыкания (в Javascript или C #) все переменные в области действия во время создания замыкания "заключены" в него?Или только переменные, на которые ссылаются во вновь созданном методе?

Пример кода C #:

private void Main() {
    var referenced = 1;
    var notReferenced = 2;  // Will this be enclosed?
    new int[1].Select(x => referenced);
}

Пример кода Javascript:

    var referenced = 1;
    var notReferenced = 2;  // Will this be enclosed?
    var func = function () {
        alert(referenced);
    }

(Вопрос пришел ко мнепри чтении утечек памяти в IE путем создания циклических ссылок с Javascript-замыканиями. http://jibbering.com/faq/notes/closures/#clMem)
Примечание: под словом «вложенный» я имею в виду то, что MSDN назвал бы «захваченным». (http://msdn.microsoft.com/en-us/library/bb397687.aspx)

Ответы [ 5 ]

18 голосов
/ 21 июля 2011

У вас есть два вопроса здесь.В будущем вы можете рассмотреть возможность публикации двух отдельных вопросов, когда у вас есть два вопроса.

При создании замыкания в Javascript все переменные в области действия во время создания замыкания «заключены» в него?

Вы не указываете, о какой из версий «JavaScript» вы говорите.Я предполагаю, что вы говорите о правильной реализации языка ECMAScript 3.Если вы говорите о какой-то другой версии «JavaScript», скажите, о какой вы говорите.

В ECMAScript 5 обновлены правила работы лексических сред и "eval".Я не являюсь членом Технического комитета 39 с 2001 года, и я не следил за последними изменениями в спецификации ECMAScript 5;если вы хотите получить ответ в контексте последних правил ECMAScript 5, найдите эксперта по этой спецификации;Я не тот.

Ответ на ваш вопрос в контексте ECMAScript 3: да .Спецификация ECMAScript 3 очень ясна в этом вопросе, но вам не нужно смотреть на эту спецификацию, чтобы знать, что это должно быть так:

function f()
{
 var referenced = 1;
 var notReferenced = 2;
 var func = function () 
 {
   alert(referenced);
   alert(eval("notReferenced"));
 }
 return func;
}
f()();

Как может "eval" работать правильно, если "notReferenced""не перехвачено?

Это все объясняется в спецификации ECMAScript, но я могу кратко подытожить это здесь.

Каждая переменная связана с" переменным объектом ", который является объектому которого есть свойства, имена которых являются именами переменных. переменный объект для функции f идентичен активационному объекту из f - то есть объекту, который магически создается каждый раз, когда вызывается f.Переменный объект имеет три свойства: «referenced», «notReferenced» и «func».

Существует переменный объект, называемый «глобальным объектом», который представляет код вне любой функции.Он имеет свойство "f".

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

С каждым объектом функции связана цепочка областей действия, которая является копией цепочки областей действия, которая действовала при создании функции.

Цепочка области действия, связанная с "f", имеет видглобальный объект.

Когда выполнение вводит "f", текущая цепочка контекста контекста выполнения имеет объект активации "f", помещенный в него.

Когда "f" создает функциюобъект, назначенный "func", связанная с ним цепочка областей действия является копией текущей цепочки областей действия контекста выполнения, то есть цепочки областей действия, содержащей активацию "f", и глобального объекта.

ОК, теперь все объекты настроены правильно.f возвращает func, который затем вызывается.Это создает объект активации для этой функции.Цепочка контекста контекста выполнения выбирается из объекта функции - помните, что это объект активации "f" плюс глобальный объект - и в эту копию цепочки контекста мы помещаем текущий объект активации.Поскольку анонимная функция, которую мы сейчас выполняем, не имеет ни аргументов, ни локальных параметров, это, по сути, запрет.

Затем мы попытаемся оценить «предупреждение»;мы ищем цепочку прицелов.Текущая активация и активация "f" не имеют ничего, называемого "предупреждением", поэтому мы запрашиваем глобальный объект, и он говорит "да", предупреждение - это функция.Затем мы оцениваем «ссылки».Он не на текущем объекте активации, но на объекте активации f, поэтому мы выбираем его значение и передаем его в оповещение.

То же самое в следующей строке.Глобальный объект сообщает нам, что существуют методы «alert» и «eval».Но, конечно, «Эвал» особенный. Eval получает копию текущей цепочки областей видимости, а также строковый аргумент .Eval анализирует строку как программу и затем выполняет программу, используя текущую цепочку областей видимости.Эта программа ищет «notReferenced» и находит его, потому что она находится в текущей цепочке областей действия.

Если у вас есть дополнительные вопросы по этой области, тогда я рекомендую вам прочитать главы 10 и 13 спецификации ECMAScript 3, пока вы не полностью не поймете их.

Давайте более внимательно посмотрим на ваш вопрос:

При создании замыкания в Javascript все переменные в области действия во время создания замыкания"заключены" в него?

Чтобы окончательно ответить на этот вопрос, вам нужно точно сказать нам, что вы подразумеваете под «замыканием» - спецификация ECMAScript 3 нигде не определяет этот термин. Под «замыканием» подразумевается объект функции или цепочка областей действия, захваченная объектом функции ?

Помните, что все эти объекты являются изменяемыми - сама цепочка областей не изменчива, но каждый объект в цепочке изменчив! Существовала или нет переменная «во время» создания либо цепочки областей действия, либо объекта функции, на самом деле немного не имеет значения; переменные приходят, переменные уходят.

Например, переменные, которые еще не были созданы на момент создания объекта функции , могут быть захвачены!

function f()
{
 var func = function () 
 {
   alert(newlyCreated);
 }
 eval("var newlyCreated = 123;");
 return func;
}
f()(); 

Очевидно, что "newCreated" не является переменной в то время, когда объект функции создается , поскольку он создается после объекта функции. Но когда вызывается функциональный объект, он появляется на объекте активации / переменной f.

Аналогично, переменная, которая существует во время создания вложенного функционального объекта, может быть удалена к моменту выполнения функционального объекта.

8 голосов
/ 21 июля 2011

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

При создании замыкания в C # все переменные в области действия во время создания замыкания "заключены" в него? Или только переменные, на которые ссылаются во вновь созданном методе?

Это зависит от того, запрашиваете ли вы де-юре или де-факто ответ.

Прежде всего, давайте уточним ваш вопрос. Мы определяем «внешнюю переменную» как локальную переменную, параметр-значение (то есть не ref или out), параметр «params» или «this» метода экземпляра, который происходит внутри метода, но вне любого лямбда-выражения или анонимного метода выражение, которое находится внутри этого метода. (Да, «this» классифицируется как «внешняя переменная», даже если она не классифицируется как «переменная». Это прискорбно, но я научился жить с этим.)

Внешняя переменная, которая используется внутри лямбда-выражения или выражения анонимного метода, называется "захваченной внешней переменной".

Итак, теперь давайте переформулируем вопрос:

При создании делегата во время выполнения, который соответствует лямбда или анонимному методу, время жизни всех внешних переменных, которые находятся в области действия во время создания делегата, увеличивается, чтобы соответствовать (или превышать) время жизни делегата? Или продлены только времена жизни захваченных внешних переменных?

Де-факто , только время жизни захваченных внешних переменных продлено. De jure , спецификация требует времени жизни захваченных внешних переменных, но не запрещает время жизни незаписанных внешних переменных.

Прочтите разделы 5.1.7, 6.5.1 и 7.15.5 спецификации C # 4 для всех деталей.

Как отмечает Джон, мы не особенно хорошо работаем над тем, чтобы минимизировать время жизни захваченных внешних переменных. Мы надеемся когда-нибудь сделать лучше.

5 голосов
/ 21 июля 2011

Я могу ответить только на стороне C #.

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

Обратите внимание, однако, что если два лямбда-выражения каждое захватывает разные переменные одной и той же области, то вВ текущей реализации оба лямбда-выражения будут использовать один и тот же синтетический класс:

private Action Foo() {
    var expensive = ...;
    var cheap = ...;
    Action temp = () => Console.WriteLine(expensive);
    return () => Console.WriteLine(cheap);
}

Здесь действие вернул все равно будет поддерживать "дорогую" ссылку в действии.Однако все это является деталью реализации компилятора Microsoft C # 4 и может измениться в будущем.

3 голосов
/ 21 июля 2011

Я могу ответить только о C #:

Нет, оно не будет заключено, потому что оно не нужно.

Я интерпретирую "это будет заключено?" в этом контексте вроде следующего:

Будет ли оно захвачено внутри замыкания, сгенерированного для лямбда-выражения, то есть будет ли в автоматически сгенерированном классе поле, содержащее значение этой переменной?

Отказ от ответственности:
Это деталь реализации. Мой ответ верен для текущей реализации компилятора C # MS для вашего конкретного случая. Это может отличаться от компилятора Mono или более новой версии компилятора MS. Или, как показывает ответ Джона, для более сложного примера он может быть даже другим.

1 голос
/ 21 июля 2011

При создании замыкания (в Javascript или C #) все переменные в области действия во время создания замыкания "заключены" в него? Или только переменные, на которые ссылаются во вновь созданном методе?

Я могу ответить только о JavaScript.

Это зависит от реализации. Некоторые движки содержат все переменные. Некоторые движки оптимизируют и содержат только переменные, на которые есть ссылки.

Я знаю, что Chrome оптимизирует и включает только те переменные, которые ему нужны.

Дополнительная информация:

Похоже, что вопрос возник из-за проблемы старой утечки памяти в IE. Это не проблема в современных браузерах.

Хорошее чтение утечек памяти, которые все еще существуют в IE8, с помощью циклических ссылок между объектами EcmaScript и Host:

Утечка памяти в IE8

Чтобы прояснить проблемы утечки памяти в IE с замыканиями, в основном речь идет о циклических ссылках между объектами Host (DOM) и JavaScript. Так что эта проблема не существует, если вы не используете DOM (el.attachEvent или что-то подобное)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...