Javascript 中的内存管理
Memory Management in Javascript
看看代码。让我们假设每条语句需要 0 毫秒才能完成。 printAfter2 是一个简单的函数,它在调用 2 秒后打印传递给它的字符串。
printAfter2 = (obj) => {
setTimeout(() => {
console.log(JSON.stringify(obj));
}, 2000)
}
在下面的代码中,我们创建了一个函数
定义块作用域变量 obj 在 time 0 ms
在 时间 0 毫秒以 obj(类型为 Object)作为参数调用函数。由于传递的参数是一个对象,因此它的引用将传递给函数。
然后是console.log函数调用。之后,块在时间 0 ms 结束,因此块作用域变量 obj 也将被销毁。
在 2000 年,printAfter2 函数获取传递给它的参数值。在这种情况下,它是一个变量的引用,到目前为止应该被销毁。但这没有按预期工作。它在 2000 毫秒 打印相同的原始 obj,它应该在 0 毫秒 被销毁。为什么会这样?
我们实际上不需要异步函数但忽略它。
(async () => {
let obj = {name: 'Ali'}
printAfter2(obj);
console.log("obj var will be destroyed after this block");
})()
当 variable/parameter obj
超出范围时,并不意味着任何东西都会立即被销毁。这仅意味着对某个对象的引用消失,这使得该对象 符合垃圾收集条件 当且仅当这是对它的最后一次引用时。垃圾收集器最终(下一次 运行s)释放属于不再可达的对象的内存,即没有对它们的引用。让我们看一个更简单的情况,没有任何闭包:
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)行将对象传递给函数;当函数执行时(例如在第 (3) 行),有两个变量引用同一个对象:o1
在外部作用域中,obj
在 f1
的作用域中。
当 f1
在第 (4) 行终止时,变量 obj
超出范围,但对象仍然可以通过 o1
访问。
第 (5) 行创建了一个新变量,再次引用同一个对象。这在概念上与将其传递给某个函数非常相似。
当 o1
停止引用第 (6) 行中的对象时,这不会使对象符合第 (7) 行中的垃圾收集条件,因为 o2
仍在引用它(“保持活动状态” ”)。只有当 o2
也被重新分配或超出范围时,对象才会变得不可访问:如果垃圾收集器 运行s 在执行到第 (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.
看看代码。让我们假设每条语句需要 0 毫秒才能完成。 printAfter2 是一个简单的函数,它在调用 2 秒后打印传递给它的字符串。
printAfter2 = (obj) => {
setTimeout(() => {
console.log(JSON.stringify(obj));
}, 2000)
}
在下面的代码中,我们创建了一个函数
定义块作用域变量 obj 在 time 0 ms
在 时间 0 毫秒以 obj(类型为 Object)作为参数调用函数。由于传递的参数是一个对象,因此它的引用将传递给函数。
然后是console.log函数调用。之后,块在时间 0 ms 结束,因此块作用域变量 obj 也将被销毁。
在 2000 年,printAfter2 函数获取传递给它的参数值。在这种情况下,它是一个变量的引用,到目前为止应该被销毁。但这没有按预期工作。它在 2000 毫秒 打印相同的原始 obj,它应该在 0 毫秒 被销毁。为什么会这样?
我们实际上不需要异步函数但忽略它。
(async () => {
let obj = {name: 'Ali'}
printAfter2(obj);
console.log("obj var will be destroyed after this block");
})()
当 variable/parameter obj
超出范围时,并不意味着任何东西都会立即被销毁。这仅意味着对某个对象的引用消失,这使得该对象 符合垃圾收集条件 当且仅当这是对它的最后一次引用时。垃圾收集器最终(下一次 运行s)释放属于不再可达的对象的内存,即没有对它们的引用。让我们看一个更简单的情况,没有任何闭包:
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)行将对象传递给函数;当函数执行时(例如在第 (3) 行),有两个变量引用同一个对象:o1
在外部作用域中,obj
在 f1
的作用域中。
当 f1
在第 (4) 行终止时,变量 obj
超出范围,但对象仍然可以通过 o1
访问。
第 (5) 行创建了一个新变量,再次引用同一个对象。这在概念上与将其传递给某个函数非常相似。
当 o1
停止引用第 (6) 行中的对象时,这不会使对象符合第 (7) 行中的垃圾收集条件,因为 o2
仍在引用它(“保持活动状态” ”)。只有当 o2
也被重新分配或超出范围时,对象才会变得不可访问:如果垃圾收集器 运行s 在执行到第 (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.