Javascript 闭包是保留整个父词法环境还是仅保留闭包引用的值的子集? [英] Does a Javascript closure retain the entire parent lexical environment or only the subset of values the closure references?

查看:29
本文介绍了Javascript 闭包是保留整个父词法环境还是仅保留闭包引用的值的子集?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下示例:

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();

因此,如果我只关闭父词法环境中的一个变量,该变量是保留下来的还是其词法环境中的每个同级变量也保留着?

So if I close around just one variable from a parent lexical environment, is that variable kept around or is every sibling variable in its lexical environment also kept around?

此外,如果 makeFunction 本身嵌套在另一个外部函数中,那么即使 makeFunction 和 makeFunction 的返回值都没有引用该外部词法环境中的任何内容,该外部词法环境是否会被保留?

Also, what if makeFunction was itself nested inside another outer function, would that outer lexical environment be retained even though neither makeFunction nor makeFunction's return value referred to anything in that outer lexical environment?

我问的是性能方面的原因——闭包是保留一堆东西还是只保留它们直接引用的东西?这会影响内存使用和资源使用(例如打开的连接、句柄等).

I'm asking for performance reasons - do closures keep a bunch of stuff around or only what they directly refer to? This impacts memory usage and also resource usage (e.g. open connections, handles, etc.).

这主要是在 NodeJS 上下文中,但也可以应用于浏览器.

This would be mostly in a NodeJS context, but could also apply in the browser.

推荐答案

V8 开发人员在这里.这有点复杂;-)

V8 developer here. This is a bit complicated ;-)

简短的回答是:闭包只保留他们需要的东西.

The short answer is: closures only keep around what they need.

因此在您的示例中,在 makeFunction 运行后,s 引用的字符串将有资格进行垃圾回收.由于垃圾收集的工作方式,无法预测它何时会被释放;在下一个垃圾收集周期".makeFunction 是否再次运行无关紧要;如果它再次运行,将分配一个新字符串(假设它是动态计算的;如果它是源中的文字,则将其缓存).made 是否已经运行或将再次运行也无关紧要;重要的是你有一个引用它的变量,所以你可以运行它(再次).引擎通常无法预测将来会执行或不会执行哪些功能.

So in your example, after makeFunction has run, the string referred to by s will be eligible for garbage collection. Due to how garbage collection works, it's impossible to predict when exactly it'll be freed; "at the next garbage collection cycle". Whether makeFunction runs again doesn't matter; if it does run again, a new string will be allocated (assuming it was dynamically computed; if it's a literal in the source then it's cached). Whether made has already run or will run again doesn't matter either; what matters is that you have a variable referring to it so you could run it (again). Engines generally can't predict which functions will or won't be executed in the future.

较长的答案是有一些脚注.一方面,正如评论已经指出的那样,如果您的闭包使用 eval,那么所有内容都必须保留,因为 eval 的任何源代码片段都可以引用任何多变的.(关于可能引用 eval 的全局变量的评论提到的不是真的;全局 eval",又名间接 eval"存在语义差异:它看不到局部变量.哪个通常被认为是性能和可调试性的优势——但更好的是根本不使用 eval.)

The longer answer is that there are some footnotes. For one thing, as comments already pointed out, if your closure uses eval, then everything has to be kept around, because whatever source snippet is eval'ed could refer to any variable. (What one comment mentioned about global variables that could be referring to eval is not true though; there is a semantic difference for "global eval", a.k.a. "indirect eval": it cannot see local variables. Which is usually considered an advantage for both performance and debuggability -- but even better is to not use eval at all.)

另一个脚注是,有点不幸的是,跟踪并不像它可能的那样细粒度:每个闭包将保留任何闭包需要的东西.我们已经尝试解决这个问题,但事实证明更细粒度的跟踪会导致更多的内存消耗(用于元数据)和 CPU 消耗(用于完成工作),因此对于实际代码通常不值得(尽管它可能对人工测试恰恰强调了这种情况).举个例子:

The other footnote is that somewhat unfortunately, the tracking is not as fine-grained as it could be: each closure will keep around what any closure needs. We have tried fixing this, but as it turns out finer-grained tracking causes more memory consumption (for metadata) and CPU consumption (for doing the work) and is therefore usually not worth it for real code (although it can have massive impact on artificial tests stressing precisely this scenario). To give an example:

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 只使用 xshort_lived 确实使用了 s(即使它永远不会被调用!),并且只有一个来自 makeFunction 的局部变量被某些闭包需要"的存储桶,因此该存储桶同时保留了 xs 活着.但正如我之前所说:真正的代码很少遇到这个问题,所以这通常不是你需要担心的.

With this modified example, even though long_lived only uses x, short_lived does use s (even if it's never called!), and there is only one bucket for "local variables from makeFunction that are needed by some closure", so that bucket keeps both x and s alive. But as I said earlier: real code rarely runs into this issue, so this is usually not something you have to worry about.

附注:

还有资源使用情况(例如打开的连接、句柄等)

and also resource usage (e.g. open connections, handles, etc.)

作为一个非常笼统的声明(即,在任何语言或运行时环境中,无论闭包或诸如此类),通常建议不要依赖垃圾收集来进行资源管理.我建议在合适的时候手动明确地释放资源.

As a very general statement (i.e., in any language or runtime environment, regardless of closures or whatnot), it's usually advisable not to rely on garbage collection for resource management. I recommend to free your resources manually and explicitly as soon as it is appropriate to free them.

这篇关于Javascript 闭包是保留整个父词法环境还是仅保留闭包引用的值的子集?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆