我知道闭包通过保存对已执行函数的引用来保持执行上下文的活跃性.
我想知道整个上下文是保存还是仅保存所需的部分.
在前一种情况下,我需要以不浪费内存的方式构造函数.无论如何,这应该是设计目标,但我想知道JavaScript是否也会处理它.
这是一个简单的例子(基于单页Web应用程序,Mikowski/Powell,第56页):
var ctx; var outer_function = function () { var dummy = 'not required for output'; var output = 'output'; var inner_function = function () { return { output: output }; } return inner_function; }; ctx = outer_function(); // returns { output: 'output' } ctx();
dummy
对象在执行后是否存储在闭包中,
outer_function
即使它不可访问也不会被使用?
我们可以看到Chrome删除了未使用的变量(除非有直接eval
调用).考虑以下代码(这里是一个小提琴):
var bar = function() { var hello = "world"; var unused = "nope"; return function(s) { console.log(hello); debugger; return s; }; } var g = bar(); g(1);
bar
返回访问内部变量的函数hello
和unused
.变量hello
在返回的函数内部使用,但unused
不是.unused
在终止后完全无法访问bar
.
当我们运行此代码时,Dev Tools已经打开(为了打破debugger
语句),我们看到:
hello在Closure范围内幸存下来.unused
已经消失了.我们可以通过转到控制台来确认这一点(当代码仍然暂停时)并且看到它hello
已定义但是访问unused
会产生ReferenceError.
这是垃圾收集的基本原则:如果变量完全无法访问,则应该释放它.没有人指定JavaScript引擎必须释放完全无法访问的变量,但这是一个明显的性能胜利,与无垃圾收集未使用的变量无法区分(在语言完整性条款中).
但是,直接调用eval
可以访问其他无法访问的变量.如果我们修改我们返回的函数以包含直接调用eval
...
var foo = function() { var hello = "world"; var unused = "nope"; return function(s) { console.log(hello); debugger; return eval(s) }; } var f = foo(); f(1);
我们看到现在保留了所有本地Closure-scope变量:
eval(s)可能会评估unused
.
您可能想知道:引擎如何可靠地检测到eval
呼叫的存在?难道你不能做一些像window["ev"+"al"](s)
发动机无法可靠检测的方式吗?答案是这种"间接" eval
调用无法访问闭包范围变量并在全局范围内执行.只有使用标识符eval
作为函数调用的一部分的直接调用才能访问局部变量,并且易于检测.
如果您有兴趣了解有关直接eval
调用的更多信息,请参阅我对global.eval的回答是无法访问词法范围中的变量.
顺便说一下,这是" eval
为邪恶" 的主要表现原因之一.eval
在代码中存在单个直接调用可防止整个闭包被垃圾收集.