ES6的const关键字会影响垃圾回收吗?

27
在Kyle Simpson的新书《You Don't Know JS: ES6 and beyond》中,我发现以下摘录:
警告:将对象或数组分配为常量意味着该值在该常量的词法范围消失之前将无法进行垃圾回收,因为对该值的引用永远无法取消设置。这可能是可取的,但如果不是您的意图,请小心!
(摘自:Simpson,Kyle。“You Don't Know JS: ES6&Beyond。”O'Reilly Media,Inc.,2015年6月2日。iBooks。此材料可能受版权保护。)
据我所知,他没有进一步阐述这一点,而且Google搜索10分钟也找不到任何相关信息。如果是这样,那么“引用永远无法取消设置”究竟是什么意思?我已经养成了将不会更改的变量声明为const的习惯,从具体性能/内存方面来说,这是一个坏习惯吗?
4个回答

27
警告:将对象或数组分配为常量意味着该值在该常量的词法作用域消失之前将无法进行垃圾回收,因为对该值的引用永远无法取消设置。这可能是可取的,但是如果这不是您的意图,请小心!这个笔记听起来有点过于警告(甚至有点愚蠢),试图从这种情况中制造某种特例。 使用const变量声明时,你不能将一些小东西(例如“”或null)分配给变量以清除其内容。就内存管理而言,这确实是唯一的区别。自动垃圾收集不受是否声明为const的影响。 所以,如果您想因任何原因(包括手动删除对某些内容引用以更快地允许垃圾收集)在未来更改变量的内容,则不要使用const。这与使用或不使用const的任何其他原因相同。如果您希望在将来任何时候更改变量包含的内容(出于任何原因),则不要使用const。对于任何理解const的人来说,这应该是完全明显的。 我认为将垃圾回收单独列为不使用const的特例似乎有些愚蠢。如果您想清除变量的内容,那么这意味着您想修改变量,所以不要使用const。是的,手动启用可能被包含在持久作用域/闭包中的大型数据结构的垃圾收集只是您将来想更改变量的一个原因。但是,这只是数百万个原因之一。所以,我再重复一遍。如果您希望将来出于任何原因更改变量的内容,则不要将其声明为const。 垃圾回收器本身不会像var或let变量一样处理const变量或其指向的内容。当它超出范围并且不再可达时,它的内容将有资格进行垃圾回收。 const具有许多优点。它允许开发人员声明某些意图,即此变量所指向的内容不应由代码更改,并且可能使运行时能够进行一些优化,因为它知道变量的内容无法更改。它还可以防止流氓或意外代码永远更改该变量的内容。在适当的情况下使用这些都是好事。通常情况下,你应该尽可能地使用const。 我还应该添加,即使一些const数据仍然可以减小大小,并使其大部分内容可供垃圾回收。例如,如果你有一个非常大的100,000元素对象数组(可能是从某些外部http调用中接收到的)在const数组中:
const bigData = [really large number of objects from some API call];

你仍然可以通过清空数组来大量减少数据的大小,这样可能会使得数组中存在的大量对象如果没有被其他内容引用,就有资格进行垃圾回收:

bigData.length = 0;

记住,const 阻止对变量名进行赋值,但不会阻止修改变量指向的内容。

您可以使用其他内置集合类型来执行相同的操作,例如 map.clear()set.clear(),甚至是任何具有减少其内存使用的方法的自定义对象/类。


2
+1 对于“傻”的评价。 :-) - Bergi
3
@KyleSimpson - 这个回答有什么不准确的地方?这里的问题是关于const变量的。你提供的链接讨论了闭包。这个问题涉及到多种关于闭包和内存回收的情况,包括最后两句话。但是总体来说,如果你想给变量赋值,就不要把它设置为const。如果你不需要对其进行赋值并且想保护它,那就把它设置为const。没有比这更复杂的了。 - jfriend00
3
@KyleSimpson - const本身不会改变垃圾回收。如果您想手动删除变量指向的引用,则不要使该变量为const - jfriend00
2
@KyleSimpson - 无论变量是用const还是var声明,它所指向的内容都将在完全相同的时刻进行垃圾回收。如果您想在将来的任何时候出于任何原因修改该变量的内容,则不要使用const - jfriend00
3
“@KyleSimpson - 你认为GC对一个变量是否是constvar有任何不同对待吗?是的,程序员必须在使用constvar时采取不同的操作方式,但不是GC。而且,如果您想在将来某个时候将变量设置为null以使某些内容符合GC的条件,则不要使用const。我的回答的第二段已经说明了这一点。那么,您认为我之前的评论哪部分是错误的?顺便说一句,我不同意你的观点,但我没有给你的答案投反对票。” - jfriend00
显示剩余9条评论

13

我笔记中的那个注释是指像这样的情况,您希望能够在其父范围的生命周期结束之前手动使某个值变得可GC:

var cool = (function(){
   var someCoolNumbers = [2,4,6,8,....1E7]; // a big array

   function printCoolNumber(idx) {
      console.log( someCoolNumbers[idx] );
   }

   function allDone() {
      someCoolNumbers = null;
   }

   return {
      printCoolNumber: printCoolNumber,
      allDone: allDone
   };
})();

cool.printCoolNumber( 10 ); // 22
cool.allDone();
在这个愚蠢的例子中,allDone()函数的目的是指出,有时候你可以决定结束对一个大型数据结构(数组、对象)的使用,即使周围的作用域或行为(通过闭包)在应用程序中持续存在。为了让GC回收该数组并释放其内存,您可以使用someCoolNumbers = null取消设置该引用。
如果您声明了const someCoolNumbers = [...];,那么您将无法这样做,因此该内存将保持使用状态,直到父作用域(通过cool上的方法拥有的闭包)在cool被取消设置或自身被GCd时消失。
更新
为了绝对清楚,在这里有一些评论线程中存在很多混淆/争论,这是我的观点: const 绝对、肯定、无可否认地影响了GC——具体来说,它影响了值在较早时间被手动GC的能力。如果该值通过const声明被引用,您将无法取消设置该引用,这意味着您不能提前将该值GC!该值只有在作用域关闭时才能被GC。
如果您想要在父作用域仍然存活的情况下手动使值变为可GC,您必须能够取消对该值的引用,如果您使用了const,则无法这样做。
有些人似乎认为我声称的是const永远防止GC。那不是我的观点。只是它防止了较早的手动GC。

6
简而言之,如果你想给变量赋值,就不要将它声明为const。如果你不需要给它赋值并希望保护它,就应该将其声明为const。就是这么简单。你只需要定义一种情况,即你可能需要给它赋值,所以在那种情况下不要将其声明为const。我坚持认为,将某物声明为const不会以任何方式改变其垃圾回收。它确实会影响你是否可以给它赋值,因此如果你想要给它分配一个新的值来替换之前分配的值,那么就不要将其声明为const - jfriend00
2
它会影响你手动释放对它的引用的能力 - 是的 - 当然你不能修改一个const变量。就像我已经说了五次一样。如果你打算将来因为任何原因改变变量的值,那么不要使用const。否则,这是一个很棒的功能。一个位于函数顶部的const变量和var变量被单独留下时,它们将在完全相同的时间被垃圾回收。 - jfriend00
3
@jfriend00,天哪,你仍然坚称一个变量会被垃圾回收。不是的,数值 会被垃圾回收。持有指向这些数值的引用的变量将防止垃圾回收。如果这些引用不能被更改,那么如果你希望它发生,就可以防止垃圾回收提前发生。我一直在反复强调的是,“const 会影响垃圾回收,因为它会防止您在作用域清除之前获取比需要更早的垃圾回收”。 - Kyle Simpson
2
@KyleSimpson:如果你想挑剔的话,变量确实会随着它们的作用域(或更早,具体取决于引擎优化)进行垃圾回收。所以你得说:“*const影响值的GC,因为它防止你删除引用,但它不影响变量的GC*”。你和jfriend在这里都是正确的,现在停止争论吧 :-) - Bergi
3
感谢回复,Kyle。我现在完全理解这个问题了,但当我读这本书时真的不明白。通常我并不难以理解你的写作 - 实际上相反 - 所以也许在下一版/更新的书中扩展一下会更值得。 - benadamstyles
显示剩余4条评论

11

不,这不会影响性能。这个注释是指帮助垃圾回收器(很少需要)的做法,通过“取消设置”变量:

{
    let x = makeHeavyObject();
    window.onclick = function() {
        // this *might* close over `x` even when it doesn't need it
    };
    x = null; // so we better clear it
}

如果您将x声明为const,显然无法这样做。

变量的生命周期(当其超出范围时)不会受到影响。但是,如果垃圾收集器出现问题,常量将始终保持其初始化值,并防止其被垃圾回收,而普通变量可能不再持有它。


1
你能详细说明一下“如果垃圾回收器出了问题”吗?这种情况会发生吗?它只会在罕见的、复杂的情况下发生吗? - benadamstyles
2
@KyleSimpson:在我的答案中,someCoolNumbers 和我的 x 有什么区别?显然,你无法明确地将 const 设置为 null,这一点是相同的。 - Bergi
2
@KyleSimpson:当函数闭合在x上时(并且存在某个点,从该点开始对函数的调用不再需要x),即使GC不是天真的,帮助垃圾收集器也是可取的。但我仍然认为这种情况很少发生,大多数情况下闭包本身会被收集,或者值不大,因此没有实际需要将其置空以避免内存泄漏。 - Bergi
3
在这种情况下,我会说性能影响是由于缺少 null 赋值,而不是因为变量被声明为 const :-) 不,我没有给你的答案投反对票。 - Bergi
2
@KyleSimpson:更有可能的原因是作者忘记了它...如果他没有忘记,他会把const改成let。或者它可能没有丢失,在const赋值时抛出异常,使应用程序崩溃 - 并且没有更多的内存可以泄漏 :-P - Bergi
显示剩余5条评论

8
垃圾回收器(GC)的工作方式是当某个东西被“无法访问”时,GC 可以安全地说这个东西不再使用,并回收该东西使用的内存。
能够替换变量的值允许我们删除对该值的引用。然而,与 var 不同,const 不能被重新赋值。因此,我们无法删除常量对该值的引用。
像变量一样,当常量“超出范围”时,例如函数退出时,且其中没有形成闭包,常量也可以被回收。

谢谢。您是说辛普森先生只是指手动取消变量不再可能吗?正如@Bergi在他的回答中所说的那样? - benadamstyles
1
谢谢你的回答,我不得不接受另一个答案,因为它更直接地回答了我的问题,但是包括你的所有答案都很好! - benadamstyles

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接