考虑以下示例:
function makeFunction() {
let x = 3;
let s = "giant string, 100 MB in size";
return () => { console.log(x); };
}
// Are both x and s held in memory here
// or only x, because only x was referred to by the closure returned
// from makeFunction?
let made = makeFunction();
// Suppose there are no further usages of makeFunction after this point
// Let's assume there's a thorough GC run here
// Is s from makeFunction still around here, even though made doesn't use it?
made();
因此,如果我仅关闭父词法环境中的一个变量,那么该变量是否保留,或者其词法环境中的每个同级变量也保留?
另外,如果 makeFunction 本身嵌套在另一个外部函数中,即使 makeFunction 和 makeFunction 的返回值都没有引用该外部词法环境中的任何内容,该外部词法环境是否会被保留?
我问的是性能原因 - 闭包保留了一堆东西还是只保留了它们直接引用的东西?这会影响内存使用和资源使用(例如打开的连接、句柄等)。
这主要适用于 NodeJS 上下文,但也可以适用于浏览器。
V8 开发者在这里。这有点复杂;-)
简短的答案是:闭包只保留他们需要的东西。
所以在你的例子中,之后makeFunction
已运行,引用的字符串s
将有资格进行垃圾收集。由于垃圾收集的工作原理,无法预测它何时会被释放; “在下一个垃圾收集周期”。无论makeFunction
再次运行并不重要;如果它再次运行,将分配一个新字符串(假设它是动态计算的;如果它是源中的文字,那么它会被缓存)。无论made
已经运行或将再次运行也没有关系;重要的是你有一个变量引用它,所以你could(再次)运行它。引擎通常无法预测哪些函数将来会执行或不会执行。
更长的答案是有一些脚注。一方面,正如评论已经指出的那样,如果您的闭包使用eval
,那么所有内容都必须保留,因为无论源代码片段是什么eval
'ed 可以指任何变量。 (一条评论提到了可能指的是全局变量eval
但这不是真的; “全局评估”(又名“间接评估”)存在语义差异:它看不到局部变量。这通常被认为是性能和可调试性方面的优势——但更好的是不使用eval
根本没有。)
另一个脚注是,有点不幸的是,跟踪并没有达到应有的细粒度:each闭包将保留什么any关闭需求。我们已经尝试修复这个问题,但事实证明,更细粒度的跟踪会导致更多的内存消耗(用于元数据)和 CPU 消耗(用于完成工作),因此对于实际代码来说通常不值得(尽管它会对人工测试恰恰强调了这种情况)。举个例子:
function makeFunction() {
let x = 3;
let s = "giant string, 100 MB in size";
let short_lived = function() { console.log(s.length); }
// short_lived(); // Call this or don't, doesn't matter.
return function long_lived() { console.log(x); };
}
let long_lived = makeFunction();
对于这个修改后的例子,即使long_lived
只使用x
, short_lived
确实使用s
(即使它从未被调用过!),并且只有一个存储桶用于“来自makeFunction
某些封闭所需要的”,这样桶就可以同时保留两者x
and s
活。但正如我之前所说:真正的代码很少遇到这个问题,所以这通常不是你需要担心的事情。
边注:
以及资源使用情况(例如打开的连接、句柄等)
作为一个非常笼统的陈述(即,在任何语言或运行时环境中,无论闭包或其他什么),通常建议不要依赖垃圾收集来进行资源管理。我建议在适当的时候手动并显式地释放资源。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)