【问题标题】:Does the Arguments object leak?Arguments 对象是否泄漏?
【发布时间】:2012-11-11 01:04:51
【问题描述】:

假设我有这个草率模式函数,它(出于某种奇怪的原因)将其 arguments 对象返回给调用者:

function example(a, b/* ...*/) {
    var c = // some processing
    return arguments;
}

存储调用结果 (var d=example();) 是否会阻止对 example 的变量环境(包含 abc 等)进行垃圾收集? Arguments object 的内部 setter 和 getter 可能仍然引用它,就像从闭包返回的函数一样。演示:

function example(a, b) {
  var c = Array(1000).fill(0); // some large object
  return {
    args: arguments,
    set: function(x) { a = x; },
    get: function() { return a; }
  };
}
var d = example('init');
console.log(d.get());
d.args[0] = 'arguments update'; // assigns the `a` variable
console.log(d.get());
d.set('variable update');
console.log(d.args); // reads the `a` variable

我知道几乎没有用例(传递 Arguments 对象被认为是不好的做法,很可能是因为它们与数组相似),但这更像是一个理论问题。不同的 EcmaScript 实现如何处理这个问题?它的实现是否接近规范?

我希望c 被垃圾回收like with a normal closure 并且不会被泄露,但是b 呢?如果我deletearguments 对象的属性会怎样?

【问题讨论】:

  • arguments 的奇怪之处在于,命名参数实际上是伪数组元素的别名。试试吧!如果你改变arguments[0],那么a 也会改变!我认为它不会因此而泄漏。
  • 真的吗?我认为这是相反的情况:arguments[0]a 的别名 :-) 规范中描述的算法表明 arguments 的索引是带有“ParameterMap”和指向调用的环境记录 - 泄漏恕我直言的可能原因。这就是为什么我还要询问实际的实现......
  • 嗯,我想这是一个谜,它是它的别名 :-) 我在想的是,因为 arguments 对象基本上 参数集,它不需要对闭包或任何东西的引用,所以除了它直接使用的内存之外,它不会“固定”其他任何东西。然而,这只是一种预感,我不知道真实的故事。
  • FWIW,严格模式切断形式参数和参数对象之间的连接。改变一个不会改变另一个。换句话说,每个都获得自己传递参数的副本
  • 哦,元效应……这会在 7 年后重新开放吗?

标签: javascript memory-leaks arguments closures


【解决方案1】:

考虑一下:

var x = function() {
  return arguments;
}
console.log( x() === x() );

这是错误的,因为它不是同一个arguments 对象:它是(对于x 的每次调用)一个新构造的对象,其中存储了所有参数的。然而它有arguments的属性:

var y = x([]);
console.log(y instanceof Object); // true
console.log(y instanceof Array);  // false
console.log(y.length); // 1
console.log(y.callee + '');       // function() { return arguments; }

但还有更多。显然,如果返回arguments,则作为参数发送到函数中的对象将不会被GC收集:

var z = x({some: 'value'});
console.log(z[0]); // {some:'value'}

这是意料之中的:毕竟,您可以通过在函数内部声明一些本地对象,将函数的第一个参数的值分配为其对象的'0'属性,然后返回该对象来获得类似的结果。在这两种情况下,引用的对象仍将“使用中”,所以我想没什么大不了的。

但是这个呢?

var globalArgs;
var returnArguments = function() {
  var localArgs = arguments;
  console.log('Local arguments: ');
  console.log(localArgs.callee.arguments); 
  if (globalArgs) { // not the first run
    console.log('Global arguments inside function: ');   
    console.log(globalArgs.callee.arguments); 
  }
  return arguments;
}
globalArgs = returnArguments('foo');
console.log('Global arguments outside function #1: ');   
console.log(globalArgs.callee.arguments);
globalArgs = returnArguments('bar');
console.log('Global arguments outside function #2: ');   
console.log(globalArgs.callee.arguments);

输出:

Local arguments: ["foo"]
Global arguments outside function #1: null
Local arguments: ["bar"]
Global arguments inside function: ["bar"]
Global arguments outside function #2: null

如您所见,如果您返回arguments 对象并将其分配给某个变量,则在函数内部,其callee.argument 属性指向与arguments 本身相同的数据集;这又是预期的。但是在函数之外variable.callee.arguments等于null(不是undefined)。

【讨论】:

  • variable.callee.argumentsarguments 不同,它是一个非标准构造。请参阅我对this question 的回复。
【解决方案2】:

如果没有对特定的 JavaScript 引擎进行任何研究,这很难得出结论性的答案。然而,我认为argumentsObjectexample 创建的上下文之间的关系与任何其他局部变量及其宿主上下文之间的关系相同。

也就是说,存储值并不需要同时存储它的上下文。

需要注意的是arguments.callee 属性是对给定arguments Object 绑定到的上下文(即Function)的引用。但是,此属性在严格模式下不存在,has been deprecated 也不存在。

除此之外,我认为可以安全地假设返回和存储 arguments Object 不会导致内存泄漏。

【讨论】:

  • argument.callee 只是引用了example 函数,不是吗?我只询问example 的执行上下文中的其他值,例如c
  • 是的,它就是这么做的。我不太确定你的意思是什么。在arguments Object 中不会引用局部变量(如c)。
  • 当然,但这就是问题所在。读取spec regarding the arguments object,参数对象的内部getter/setter确实引用了变量环境记录(包括c)。
  • 我不认为规范是这么说的。我引用:“参数对象是通过调用抽象操作 CreateArgumentsObject 和 (...) env 函数代码的变量环境 (...) 来创建的。”很确定 env 指的是封闭上下文而不是封闭上下文。
猜你喜欢
  • 2012-01-23
  • 2012-01-31
  • 1970-01-01
  • 1970-01-01
  • 2014-12-10
  • 2010-11-25
  • 2018-04-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多