问题描述
看看代码。假设每个语句需要0毫秒才能完成。 printAfter2 是一个简单的函数,可在调用2秒后打印传递给它的字符串。
printAfter2 = (obj) => {
setTimeout(() => {
console.log(JSON.stringify(obj));
},2000)
}
-
在时间0毫秒
定义块作用域变量 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)行中),有两个变量引用同一对象:外部范围内的o1
和obj
范围内的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.