Javascript中的内存管理

问题描述

看看代码。假设每个语句需要0毫秒才能完成。 printAfter2 一个简单的函数,可在调用2秒后打印传递给它的字符串。

printAfter2 = (obj) => {
    setTimeout(() => {
        console.log(JSON.stringify(obj));
    },2000)
} 

在下面的代码中,我们创建了一个函数

  • 时间0毫秒

    定义块作用域变量 obj
  • 时间0 ms 调用以obj(类型为Object)作为参数的函数。由于传递的参数是一个对象,因此其引用将传递给该函数

  • 然后有console.log函数调用。之后,该块在时间 0 ms 结束,因此该块范围变量 obj 也将被破坏。

  • 在时间2000, printAfter2 函数获取传递给它的参数的值。在这种情况下,它是到目前为止应该销毁的变量的引用。但这没有按预期工作。它在 2000 ms 上打印相同的原始 obj ,应该在 0 ms 上销毁。为什么会这样?

我们实际上不需要异步功能,但可以忽略它。

(async () => {
    let obj = {name: 'Ali'}
    printAfter2(obj);
    console.log("obj var will be destroyed after this block");
})()

解决方法

当变量/参数obj超出范围时,这并不意味着任何内容都会立即被销毁。它仅意味着对某个对象的引用消失了,这使得该对象符合条件,并且且仅当该对象是对它的最后引用时才可进行垃圾回收。垃圾收集器最终(将在其下一次运行时)最终释放属于不再可访问的对象的自由内存,即没有对其的引用。让我们看一个没有任何闭包的简单情况:

let o1;
function f1(obj) {
  console.log(obj);  // (3)
}                    // (4)
o1 = new Object();   // (1)
f1(o1);              // (2)
let o2 = o1;         // (5)
o1 = null;           // (6)
// (7)
o2 = new Array();
// (8)

第(1)行显然分配了一个Object,并使用变量o1来引用它。注意,对象和变量之间是有区别的。特别是它们的寿命不同。 第(2)行将Object传递给函数;在函数执行时(例如,在第(3)行中),有两个变量引用同一对象:外部范围内的o1obj范围内的f1。 当f1在第(4)行终止时,变量obj超出范围,但仍可以通过o1到达对象。 第(5)行创建一个新变量,再次引用同一对象。从概念上讲,这与将其传递给某些功能非常相似。 当o1在第(6)行中停止引用该对象时,这并不意味着该对象有资格在第(7)行中进行垃圾回收,因为o2仍在引用该对象(“保持活动状态”)。仅一旦o2也被重新分配或超出范围,该对象就无法访问:如果垃圾收集器在执行到达第(8)行后的任何时间运行,则该对象的内存将被释放。

(附带说明:垃圾收集器实际上并不“收集垃圾”或“销毁对象”,因为它根本不接触该内存。它仅记录以下事实:存储对象的内存现在已免费用于新分配。)

在您的示例中,您正在创建一个闭包() => console.log(JSON.stringify(obj)),其中包含对该对象的引用。尽管此闭包在等待执行时间,但此引用将使对象保持活动状态。它只能在关闭运行后释放,并且自身无法访问。

以另一种方式进行说明:

function MakeClosure() {
  let obj = {message: "Hello world"};
  return function() { console.log(JSON.stringify(obj)); };
}

let callback = MakeClosure();
// While the local variable `obj` is inaccessible now,`callback` internally
// has a reference to the object created as `{message: ...}`.
setTimeout(callback,2000);
// Same situation as above at this point.
callback = null;
// Now the variable `callback` can't be used any more to refer to the closure,// but the `setTimeout` call added the closure to some internal list,so it's
// not unreachable yet.
// Only once the callback has run and is dropped from the engine-internal list
// of waiting setTimeout-scheduled callbacks,can the `{message: ...}` object get
// cleaned up -- again,this doesn't happen immediately,only whenever the garbage 
// collector decides to run.