您有两个问题。将来,当您有两个问题时,您可能会考虑发布两个单独的问题。
在 Javascript 中创建闭包时,创建闭包时范围内的所有变量是否都“封闭”在其中?
您没有说明您正在谈论的“JavaScript”的多个版本中的哪一个。我将假设您正在谈论 ECMAScript 3 语言的正确实现。如果您在谈论其他版本的“JavaScript”,请说出您在谈论哪个版本。
ECMAScript 5 更新了关于词法环境和“eval”如何工作的规则。自 2001 年以来,我就不再是 Technical Committee 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()();
如果未捕获“notReferenced”,“eval”如何正常工作?
这在 ECMAScript 规范中都有解释,但我可以在这里简单总结一下。
每个变量都与一个“变量对象”相关联,该对象是具有名称为变量名称的属性的对象。函数 f 的 变量对象 与 f 的 activation 对象 相同——即每次调用 f 时都会神奇地创建的对象。变量对象具有三个属性:“referenced”、“notReferenced”和“func”。
有一个变量对象叫做“全局对象”,它代表任何函数之外的代码。它有一个属性“f”。
每个执行上下文都有一个作用域链,它是在尝试评估标识符时搜索其属性的对象列表。
每个函数对象都有一个与之关联的作用域链,它是创建函数时有效的作用域链的副本。
与“f”关联的作用域链是全局对象。
当执行进入“f”时,执行上下文的当前作用域链将“f”的激活对象推送到它上面。
当“f”创建分配给“func”的函数对象时,其关联的作用域链是执行上下文的当前作用域链的副本——即包含“f”激活的作用域链,和全局对象。
好的,现在所有对象都已正确设置。 f 返回 func,然后调用它。这会为该函数创建一个激活对象。执行上下文的作用域链是从函数对象中获取的——记住,它是“f”的激活对象加上全局对象——然后我们将当前激活对象推送到作用域链的副本上。由于我们现在执行的匿名函数既没有参数也没有局部变量,这本质上是一个空操作。
然后我们尝试评估“警报”;我们查找范围链。当前激活和“f”激活没有任何称为“alert”的东西,所以我们询问全局对象,它说是,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()();
显然,“newlyCreated”在函数对象创建时不是一个变量,因为它是在函数对象之后创建的。但是当函数对象被调用时,它就在 f 的激活/变量对象上。
同样,在创建嵌套函数对象时存在的变量可以在函数对象执行时被删除。