是否需要引用变量才能包含在闭包中?

Pro*_*one 2 javascript c# closures

在创建闭包时(在Javascript或C#中),闭包创建时范围内的所有变量是否"封闭"在其中?或者只是新创建的方法中引用的变量?

示例C#代码:

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

示例Javascript代码:

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

(通过使用Javascript闭包创建循环引用来阅读IE中的内存泄漏时,问题来了.http
://jibbering.com/faq/notes/closures/#clMem)注意:带有"封闭"一词,我的意思是MSDN会叫"被抓".(http://msdn.microsoft.com/en-us/library/bb397687.aspx)

Eri*_*ert 18

你有两个问题.将来,如果您有两个问题,可以考虑发布两个单独的问题.

在Javascript中创建闭包时,闭包创建时范围内的所有变量是否"封闭"在其中?

您没有说明您正在谈论的"JavaScript"的许多版本中的哪一个.我将假设您正在讨论ECMAScript 3语言的正确实现.如果您正在谈论其他版本的"JavaScript",请说出您正在谈论的那个版本.

ECMAScript 5更新了有关词汇环境和"评估"如何工作的规则.自2001年以来,我还没有成为技术委员会39的成员,而且我还没有及时了解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()();
Run Code Online (Sandbox Code Playgroud)

如果未捕获"notReferenced","eval"如何正常工作?

这一点在ECMAScript规范中都有解释,但我可以在这里简单总结一下.

每个变量都与一个"变量对象"相关联,该变量对象具有名称为变量名称的属性.函数f 的变量对象与f的激活对象相同- 也就是说,每次调用f时都会神奇地创建对象.变量对象有三个属性:"referenced","notReferenced"和"func".

有一个名为"全局对象"的变量对象,它表示任何函数之外的代码.它有一个属性"f".

每个执行上下文都有一个作用域链,它是在尝试评估标识符时搜索其属性的对象列表.

每个函数对象都有一个与之关联的作用域链,它是在创建函数时生效的作用域链的副本.

与"f"关联的范围链是全局对象.

当执行进入"f"时,执行上下文的当前作用域链将"f"的激活对象压入其中.

当"f"创建分配给"func"的函数对象时,其关联的作用域链是执行上下文的当前作用域链的副本 - 即包含激活"f"的作用域链,以及全局宾语.

好的,现在所有对象都设置正确.f返回func,然后调用它.这为该功能创建了一个激活对象.执行上下文的作用域链是从函数对象中获取的 - 记住,它是"f"的激活对象加上全局对象 - 并且在作用域链的副本上我们推送当前的激活对象.由于我们现在执行的匿名函数既没有参数也没有本地函数,这实际上是一个无操作.

然后我们尝试评估"警报"; 我们查看范围链.当前激活和"f"激活没有任何称为"警报"的东西,所以我们询问全局对象,它说是,alert是一个函数.然后我们评估"引用".它不在当前激活对象上,而是在f的激活对象上,因此我们获取其值并将其传递给警报.

下一行也是一样.全局对象告诉我们有方法"alert"和"eval".但当然"eval"很特别.Eval获取当前作用域链的副本以及字符串参数.Eval将字符串解析为程序,然后使用当前作用域链执行程序.该程序查找"notReferenced"并找到它,因为它位于当前作用域链上.

如果您对此领域有更多疑问,我建议您阅读ECMAScript 3规范的第10章和第13章,直到您完全理解它们为止.

让我们更深入地看看你的问题:

在Javascript中创建闭包时,闭包创建时范围内的所有变量是否"封闭"在其中?

为了明确地回答这个问题,您需要准确地告诉我们"闭包"的含义 - ECMAScript 3规范没有定义该术语."闭包"是指函数对象捕获的函数对象作用域链

请记住,所有这些对象都是可变的 - 范围链本身不可变,但链中的每个对象都是可变的!是否在创建范围链或函数对象时" 存在" 变量实际上有点无关紧要; 变量来了,变量去了.

例如,可以捕获在创建函数对象时尚未创建的变量!

function f()
{
 var func = function () 
 {
   alert(newlyCreated);
 }
 eval("var newlyCreated = 123;");
 return func;
}
f()(); 
Run Code Online (Sandbox Code Playgroud)

很明显,"newlyCreated" 在创建函数对象时不是变量,因为它是在函数对象之后创建的.但是当调用函数对象时,它就是f的激活/变量对象.

类似地,在创建嵌套函数对象时存在的变量可以在执行函数对象时删除.

  • @Raynos:你怎么知道解析时是否提到'eval'?你无法知道*分析*时间; 你只能在*run*time知道它.在E3中,在全球范围内说'var whatever ="hello"是完全合法的; window [whatever] = eval;`然后在一个完全不同的函数中说`hello("code");`和嘿,`hello`是`eval`的同义词.也许在E5中你不允许用eval做疯狂的事情; 我没看过规格.但在E3中你绝对可以这么做. (2认同)

Eri*_*ert 8

你有两个问题.将来,如果您有两个问题,可以考虑发布两个单独的问题.

在C#中创建闭包时,闭包创建时范围内的所有变量是否都"封闭"在其中?或者只是新创建的方法中引用的变量?

这取决于你是否要求法律上事实上的答案.

首先,让我们澄清你的问题.我们将"外部变量"定义为局部变量,值参数(即,不是ref或out),"params"参数,或者在方法内部但在任何lambda或匿名方法之外发生的实例方法的"this"表达式在该方法中.(是的,"这个"被归类为"外部变量",即使它没有被归类为"变量".这是不幸的,但我已经学会了忍受它.)

在lambda或匿名方法表达式中使用的外部变量称为"捕获的外部变量".

那么现在让我们重新提出问题:

在运行时创建与lambda或匿名方法相对应的委托时,在委托创建时范围内的所有外部变量的生命周期是否扩展到匹配(或超过)委托的生命周期?或者只是延长了捕获的外部变量的生命周期?

事实上,只有被捕获的外部变量的生命周期才会延长. 在法律上,规范要求延长捕获的外部变量的生命周期,但不禁止延长未捕获的外部变量的生命周期.

有关所有细节,请阅读C#4规范的5.1.7,6.5.1和7.15.5节.

正如Jon指出的那样,我们并没有特别好地确保捕获的外部变量的生命周期最小化.我们希望有一天会做得更好.


Jon*_*eet 5

我只能回答C#方面的问题.

如果闭包实际上没有捕获变量,它将作为方法中的常规局部变量保留.如果它被捕获,它将仅被提升为合成类中的实例变量.(当我谈到变量被"封闭"时,我假设你正在谈论的是什么.)

但请注意,如果两个lambda表达式各自捕获相同作用域的不同变量,则在当前实现中,两个 lambdas将使用相同的合成类:

private Action Foo() {
    var expensive = ...;
    var cheap = ...;
    Action temp = () => Console.WriteLine(expensive);
    return () => Console.WriteLine(cheap);
}
Run Code Online (Sandbox Code Playgroud)

这里返回的操作仍然会保持"昂贵"的引用.所有这些都是Microsoft C#4编译器的实现细节,并且可能在将来发生变化.