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的激活/变量对象.
类似地,在创建嵌套函数对象时存在的变量可以在执行函数对象时删除.
你有两个问题.将来,如果您有两个问题,可以考虑发布两个单独的问题.
在C#中创建闭包时,闭包创建时范围内的所有变量是否都"封闭"在其中?或者只是新创建的方法中引用的变量?
这取决于你是否要求法律上或事实上的答案.
首先,让我们澄清你的问题.我们将"外部变量"定义为局部变量,值参数(即,不是ref或out),"params"参数,或者在方法内部但在任何lambda或匿名方法之外发生的实例方法的"this"表达式在该方法中.(是的,"这个"被归类为"外部变量",即使它没有被归类为"变量".这是不幸的,但我已经学会了忍受它.)
在lambda或匿名方法表达式中使用的外部变量称为"捕获的外部变量".
那么现在让我们重新提出问题:
在运行时创建与lambda或匿名方法相对应的委托时,在委托创建时范围内的所有外部变量的生命周期是否扩展到匹配(或超过)委托的生命周期?或者只是延长了捕获的外部变量的生命周期?
事实上,只有被捕获的外部变量的生命周期才会延长. 在法律上,规范要求延长捕获的外部变量的生命周期,但不禁止延长未捕获的外部变量的生命周期.
有关所有细节,请阅读C#4规范的5.1.7,6.5.1和7.15.5节.
正如Jon指出的那样,我们并没有特别好地确保捕获的外部变量的生命周期最小化.我们希望有一天会做得更好.
我只能回答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编译器的实现细节,并且可能在将来发生变化.