复杂闭包分析

闭包

JavaScript 开发的一个关键方面就是闭包:一个可以访问外部(封闭)函数变量的内部函数。由于 JavaScript 运行时的实现细节,可以通过以下方式泄漏内存:

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
    if (originalThing) // a reference to 'originalThing'
      console.log("hi");
  };
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log("message");
    }
  };
};
setInterval(replaceThing, 1000);

  

这个代码片段做了一件事:每次调用 replaceThing 时,theThing 都会获得一个新对象,它包含一个大的数组和一个新的闭包(someMethod)。同时,变量 unused 保留了一个拥有 originalThing 引用的闭包(前一次调用 theThing 赋值给了 originalThing)。已经有点混乱了吗?重要的是,一旦一个作用域被创建为闭包,那么它的父作用域将被共享。

在这个例子中,创建闭包 someMethod 的作用域是于 unused 共享的。unused 拥有 originalThing 的引用。尽管 unused 从来都没有使用,但是 someMethod 能够通过 theThing 在 replaceThing 之外的作用域使用(例如全局范围)。并且由于 someMethod 和 unused 共享 闭包范围,unused 的引用将强制保持 originalThing 处于活动状态(两个闭包之间共享整个作用域)。这样防止了垃圾回收。
当这段代码重复执行时,可以观察到内存使用量的稳定增长。当 GC 运行时,也没有变小。实质上,引擎创建了一个闭包的链接列表(root 就是变量 theThing),并且这些闭包的作用域中每一个都有对大数组的间接引用,导致了相当大的内存泄漏,如下图:

image

这个问题由 Meteor 团队发现的,他们有一篇伟大的文章,详细描述了这个问题。

原文地址:https://www.cnblogs.com/jacksplwxy/p/9546739.html